/* Kernel Module that put all together for Dyn Phase Pred-n, PMC logging and DVFS */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <asm/apic.h>
#include <asm/msr.h>
#include <asm/kperf.h>
#include <asm/io.h>
/* For tty */
#include <linux/sched.h> /* For current */
#include <linux/tty.h> /* For the tty declarations */
#include <linux/version.h>	/* For LINUX_VERSION_CODE */

#include "CANO_PHASE_DVFS_LKM.h"

#define MODULE_NAME "CANO_PHASE_DVFS_LKM"
#define ENTER 1
#define EXIT 0
#define PARALLEL_PORT_ADDR 0x378
#define PPORT_SIGNALING 1
/* Interrupt handler will signal on pport at interrupt enters/exits */
/* BIT MANIPULATIONS FOR PPORT SIGNALING */
#define setbit(data,bitnum)\
        ( data |= (1 << bitnum) )
#define clearbit(data,bitnum)\
        ( data &= ~(1 << bitnum) )
#define flipbit(data,bitnum)\
        ( data ^= (1 << bitnum) )
#define testbit(testdata,bitnum)\
        ((((0x00000000 | (1 << bitnum)) & testdata)==0) ? 0 : 1)


extern void * sys_call_table[];
extern perf_handler_t perf_handler;
extern thermal_handler_t thermal_handler;

typedef unsigned long uint32;
typedef unsigned long long uint64;

/* Old syscall references */
void * old_refs[MAX_SYSCALLS];
void * DVFS_old_refs[DVFS_MAX_SYSCALLS];

perf_handler_t old_perf_handler;
thermal_handler_t old_thermal_handler;

int sys_rd_msr(uint32 msr, unsigned long *val);
int sys_wr_msr_bit(uint32 msr, uint32 bit_num, uint32 val);
int sys_wr_msr_32(uint32 msr, uint32 val);
void print_toTTY(char *str);

/* GLOBAL STUFF FOR KERNEL SAMPLING */
int sampling_delta_ins = -100*1000000; /* So it'll OVF after this many ins [100M ins]*/
unsigned long long kernel_samples[MAX_SAMPLES][NUM_ind];
int current_sample_no = 0;
int PMU_logging = 0;

/* GLOBAL STATE FOR GPHT - initialize in init_module*/
int GPHR[GPHR_depth]; 
int PHT_tag[PHT_entries][GPHR_depth]; 
int PHT_pred[PHT_entries][1]; 
int PHT_age[PHT_entries][1]; /*1-30000: ages; -1: invalid */
/* predn type */
enum PREDN_TYPES {Mem_LastValue=0, Mem_GPHT, Const_HiPwr, NUM_PREDN};
int PREDN_TYPE = Mem_GPHT;

/* Machine config for DVFS */
enum  machines {DEVBOARD=0, LAPTOP=1, NUM_MACHINES};
short machine = LAPTOP;

/* GLOBAL STATE FOR CONVERTING PHASE to DVFS FREQ SETTING */
unsigned int Phase2FREQmsr[6];

int enable_thermal_interrupt(void)
{
  unsigned long enable_msr = 0;
  unsigned long ctr_msr = 0;
  unsigned long thermal_interrupt_msr = 0;

      /*Active TM2 by default */
      sys_wr_msr_bit(MSR_IA32_MISC_ENABLE,3,1);
      sys_wr_msr_bit(MSR_IA32_THERM_CONTROL,16,1);
      /* Verify...*/
      sys_rd_msr(MSR_IA32_MISC_ENABLE,&enable_msr);
      sys_rd_msr(MSR_IA32_THERM_CONTROL,&ctr_msr);

	if(enable_msr & (1<<3))
	  {
	    /* Thermal control enabled... GOOD! 
	       Which mode is active?*/
	    if((ctr_msr & (1<<16)))
	      printk("TM2 mode enabled\n");
	    else
	      printk("TM1 mode enabled\n");

	    printk("0x%08x\n",(unsigned int) ctr_msr);
	  }
	else
	  {
	    printk("Failed to enable TM2!\n");
	    return -1;
	  }

	/* enable interrupt... UP & DOWN */
  sys_wr_msr_bit(MSR_IA32_THERM_INTERRUPT,0,1);
  sys_wr_msr_bit(MSR_IA32_THERM_INTERRUPT,1,1);
  /* Vefiry...*/
  sys_rd_msr(MSR_IA32_THERM_INTERRUPT, &thermal_interrupt_msr);
  if(!(thermal_interrupt_msr & (0x1)))
    {
      printk("Failed to enable thermal interrupt!");
      return -1;
    }

  return 0; // All OK!

}


/*
 * PERF HNADLER IS THIS MAN
 */
void new_perf_handler(struct pt_regs *regs)
{
  char msg_str[100]; /* At most 100 char message */  
  counters_t counters;
  int i,j;
  /* pred-n vars */
  int curr_METRIC2_value_x1000; /* x1000 to avoid FP comps */
  int curr_METRIC2_phase; int pred_METRIC2_phase;

  static int match = 0; static int matching_ind=0;
  /* These have to be static becoz it has a sample-carried-dep */
  /*in the new sample, we know if we had a match in PHT, from match's value*/
  /*i.e. if it is 1, then GPHR hit PHT in last sample, and we predicted based on PHT_pred*/
  /*so this sample, we need to update PHT_pred based on our actual observation for the METRIC2 value, */
  /*and matching_ind tells where we had the hit in PHT!*/
  /*The offline 'DVSPredict.c' didn't need this, as we knew curr_METRIC2_phase at */
  /*the time of pred-n from the sample log*/ /*dam!!*/
  
  /* DVFS vars*/
  unsigned int new_PERF_CTL_MSR_value, old_PERF_CTL_MSR_value, high;
  
  char Pdata_in; /* PPort data */

  stop_both_event_counters(); /* FIRST STOP */ 
  
  #if PPORT_SIGNALING
    /* SET BIT1 WHEN ENTERED INSIDE INTERRUPT ROUTINE */
    Pdata_in = inb(PARALLEL_PORT_ADDR);
    setbit(Pdata_in,1);
    outb(Pdata_in, PARALLEL_PORT_ADDR);
  #endif
  
  
  /* TO TTY: */
   readpmu(&counters);
//   sprintf(msg_str,"Counter 0: %lld\n",counters.counter0);
//   print_toTTY(msg_str);
//   sprintf(msg_str,"Counter 1: %lld\n",counters.counter1);
//   print_toTTY(msg_str);
//   sprintf(msg_str,"TSC      : %lld\n",counters.clock);
//   print_toTTY(msg_str);
//   /* YOU CANNOT DO FP ARITH IN KERNEL MOD, SO I DO BELOW STUPID TRICK TO GET IPC, etc */
//   sprintf(msg_str,"UPC0  : %d/100\n", (-1 * sampling_delta_ins) / ((int) counters.clock / 100) );
//   print_toTTY(msg_str);
// //  sprintf(msg_str,"UPC1  : %d/100\n", (int) counters.counter1 / ((int) counters.clock / 100) );
// //  sprintf(msg_str,"BUS_DATA_RCV / CYC  : %d/10000\n", (int) counters.counter1 / ((int) counters.clock / 10000) );
//   sprintf(msg_str,"BUS_TRAN_MEM / CYC  : %d/10000\n", (int) counters.counter1 / ((int) counters.clock / 10000) );
//   print_toTTY(msg_str);
// //  sprintf(msg_str,"BUS_DATA_RCV / Uop  : %d/10000\n", (int) counters.counter1 / ((-1 * sampling_delta_ins) / 10000) );
//   sprintf(msg_str,"BUS_TRAN_MEM / Uop  : %d/10000\n", (int) counters.counter1 / ((-1 * sampling_delta_ins) / 10000) );
//   print_toTTY(msg_str);
  
  /*********************************************************/
  /* GPHT OPS (I'll avoid func-n calling as much as i can) */
  /*********************************************************/
  /* 1) Convert Counter Reading --> Mem/Uop --> phase */
  /* counter1 has BUS_TRAN_MEM count (mem accesses), delta_ins always 100M */
  /* Counter Reading --> Mem/Uop (METRIC2) */
  curr_METRIC2_value_x1000 = ((int) counters.counter1) / (-1 * (sampling_delta_ins/1000));
  /* Mem/Uop --> phase *//*this i can do w/ a way more efficient macro i think if need be*/
  /* BELOW IS HOW I DID IT TO AVOID FP OPS! IT'S ESSENTIALLY SAME AS OFFLINE DVSPredict's Phase func-n */
  if ( curr_METRIC2_value_x1000 <= 5)
  {
	  curr_METRIC2_phase = 1;
  }
  else if ( (curr_METRIC2_value_x1000 > 5) && (curr_METRIC2_value_x1000 <= 10) )
  {
	  curr_METRIC2_phase = 2;
  }
  else if ( (curr_METRIC2_value_x1000 > 10) && (curr_METRIC2_value_x1000 <= 15) )
  {
	  curr_METRIC2_phase = 3;
  }
  else if ( (curr_METRIC2_value_x1000 > 15) && (curr_METRIC2_value_x1000 <= 20) )
  {
	  curr_METRIC2_phase = 4;
  }
  else if ( (curr_METRIC2_value_x1000 > 20) && (curr_METRIC2_value_x1000 <= 30) )
  {
	  curr_METRIC2_phase = 5;
  }
  else if ( (curr_METRIC2_value_x1000 > 30) )
  {
	  curr_METRIC2_phase = 6;
  }
  else
  {
    curr_METRIC2_phase = -1;
    print_toTTY("THIS SHOULDN'T HAPPEN! I couldn't assign curr_METRIC2_phase!!\n");
    printk("THIS SHOULDN'T HAPPEN! I couldn't assign curr_METRIC2_phase!!\n");
  }
 
  /* 2) Update PHT states based on whether we had a match in PHT last sample */
  if (match)
  {
	  PHT_pred[matching_ind][0] = curr_METRIC2_phase;
	  /* here we can add bimodal like structs */
	  PHT_age[matching_ind][0] = 0;
  }
  else
  {
	  /* Find the first unoccupied PHT entry */
	  for (i=0; i<PHT_entries; i++)
	  {
	     if (PHT_age[i][0] == -1)
	     {
		for (j=0; j<GPHR_depth; j++)
		{
			PHT_tag[i][j] = GPHR[j];
		}
		PHT_age[i][0] = 0;
		PHT_pred[i][0] = curr_METRIC2_phase;
		break;
	     }
	  }
	  if (i==PHT_entries) /* So all PHT is already full */
	  {
	     int oldest_age = 0, oldest_index = 0;

	     /* find the oldest untouched entry (i can merge it w/ above if need)*/
	     for (i=0; i<PHT_entries; i++)
	     {
		if (PHT_age[i][0] > oldest_age)
		{
			oldest_age = PHT_age[i][0];
			oldest_index = i;
		}
	     }
 	     for (j=0; j<GPHR_depth; j++)
	     {
		PHT_tag[oldest_index][j] = GPHR[j];
	     }
	     PHT_age[oldest_index][0] = 0;
	     PHT_pred[oldest_index][0] = curr_METRIC2_phase;
	     //break; --> this was F-ing up all my results
	  }
  }
  
  /* 3) Update GPHR */
  /* GPHR[0] has the last value, GPHR[GPHR_depth-1] has the oldest history */
  for (j=(GPHR_depth-1); j>0; j--)
  {
	 GPHR[j] = GPHR[j-1];
  }
  GPHR[0]  = curr_METRIC2_phase;
  
  /* 4) Update ages w/ sat cntr (also can merge)*/
  for (i=0; i<PHT_entries; i++)
  {
	  if ( (PHT_age[i][0] != -1) && (PHT_age[i][0] < 32000) )
	  {
		  PHT_age[i][0] ++;
	  }
  }

  /* 5) <<<< FINALLY predict next sample >>>> */
  /* 5.1) Check for a valid match betw GPHR <-> PHT tags */
  for (i=0; i<PHT_entries; i++)
  {
	  match = 1;
	  for (j=0; j<GPHR_depth; j++)
	  {
		  match = match & (GPHR[j]==PHT_tag[i][j]);
	  }
	  match = match & (PHT_age[i][0] != -1);
	  if (match) 
	  {
		  matching_ind = i;
		  break;
	  }
  }
  /* 5.2) Predict */
  if (PREDN_TYPE == Mem_GPHT)
  {
     if (match)
     {
	     /* do the prediction based on PHT */
	     pred_METRIC2_phase = PHT_pred[matching_ind][0];
     }
     else
     {
	     /* do the prediction based on LastVal */
	     pred_METRIC2_phase = GPHR[0];
     }
  }
  else if (PREDN_TYPE == Mem_LastValue)
  {
     pred_METRIC2_phase = GPHR[0]; /* do the prediction based on LastVal */
  }
  else if (PREDN_TYPE == Const_HiPwr)
  {
     pred_METRIC2_phase = 1; /* constantly predict next phase as the hipower phase */
  }
  else
  {
    printk(" GOT UNKNOWN PREDN TYPE!!\n");
    print_toTTY(" GOT UNKNOWN PREDN TYPE!!\n");
  }
  /*********************************************************/
  /* EO GPHT OPS */
  /*********************************************************/


  /*********************************************************/
  /* KERNEL's SAMPLE LOG */
  /*********************************************************/
  if ( (PMU_logging) && (current_sample_no < MAX_SAMPLES) )
  {
     kernel_samples[current_sample_no][CNTR0_ind] = counters.counter0;
     kernel_samples[current_sample_no][CNTR1_ind] = counters.counter1;
     kernel_samples[current_sample_no][TSC_ind]   = counters.clock;
     kernel_samples[current_sample_no][real_PHASE_ind]   = (unsigned long long) curr_METRIC2_phase;
     kernel_samples[current_sample_no+1][pred_PHASE_ind] = (unsigned long long) pred_METRIC2_phase;

     current_sample_no ++;
  }
//  sprintf(msg_str,"current_sample_no: %d\n", current_sample_no);
//  print_toTTY(msg_str);
  /*********************************************************/
  /*********************************************************/
    

  /*********************************************************/
  /* Assigning DVFS STate Based ON PREDICTED PHASE: */
  /*********************************************************/
  if ((pred_METRIC2_phase >= 1) && (pred_METRIC2_phase <= 6))
  {
    new_PERF_CTL_MSR_value = Phase2FREQmsr[pred_METRIC2_phase-1];
    rdmsr(MSR_IA32_PERF_CTL, old_PERF_CTL_MSR_value, high);
    old_PERF_CTL_MSR_value &= ~0xffff; /* zeros out the low 16 bits */
    //new_PERF_CTL_MSR_value &= 0xffff;  /* just to be sure */
    old_PERF_CTL_MSR_value |= new_PERF_CTL_MSR_value;  /* combine it into 32 bits */
    wrmsr(MSR_IA32_PERF_CTL, old_PERF_CTL_MSR_value, high);
  }
  else
  {
    /* This is bizarre case!! If this happens don't do any DVFS */
    print_toTTY("THIS SHOULDN'T HAPPEN! I reached a pred_METRIC2_phase not in {1,2,...,6} for DVFS mode set!!\n");
    printk("THIS SHOULDN'T HAPPEN! I reached a pred_METRIC2_phase not in {1,2,...,6} for DVFS mode set!!\n");
  }
  /*********************************************************/
  /*********************************************************/
    

  /*********************************************************/
  /* Prepare to EXIT: */
  /*********************************************************/
  reset_both_event_counters();
  wrmsr(0x10, 0, 0); /* also reset TSC *//* CANO-2005 */
  
  /* SET CNTR0 TO -DELTA_INS SO IT OVF'S AFTER DELTA_INS */
  sys_wr_msr_32(MSR_P6_PERFCTR0, sampling_delta_ins); /* for delta_ins samplin */
  
  #if PPORT_SIGNALING
    /* CLEAR BIT1 WHEN EXITING INTERRUPT ROUTINE */
    //Pdata_in = inb(PARALLEL_PORT_ADDR);
    clearbit(Pdata_in,1);
    /* also flip BIT0 to tell we moved to next sample */
    flipbit(Pdata_in,0);
    outb(Pdata_in, PARALLEL_PORT_ADDR);
  #endif

  start_both_event_counters(); /* LAST START */
}

void new_thermal_handler(struct pt_regs *regs)
{
  unsigned long thermal_status_msr=0;
  sys_rd_msr(MSR_IA32_THERM_STATUS, &thermal_status_msr);
  if( (thermal_status_msr & 0x01) ) {
    /* We are at the trip temperature!*/
    printk("Too HOT!\n");
  }
  else {
    printk("That's better :)\n");
  }

}

/**************************************************************/
/* SYSCALLS */
/**************************************************************/
#define KERNEL_SAMPLING
int sys_flagComponent (int type, int flag)
{
#if defined(KERNEL_SAMPLING)
  char data = (char)flag;
  char in_data;

  if(type==ENTER)
    {
      in_data = inb(PARALLEL_PORT_ADDR);
      data |= (char)in_data;
      //      component_flag = (unsigned long long) flag;
      outb(data, PARALLEL_PORT_ADDR);
    }
  else
    {
      in_data = inb(PARALLEL_PORT_ADDR);
      in_data &=~(data);
      outb(in_data,PARALLEL_PORT_ADDR);
      //      component_flag = 0;
    }

#else 
    uint32 low, high;
    uint64 cnt0, cnt1, ccnt;

    rdpmc(0, low, high);
    cnt0 = ( ((uint64)high)<<32 ) | low;
    /*keep only low 40bits, but this seems unnecessary as pmc ony read 8bits into high */
    cnt0 &= 0xffffffffff;  

    rdpmc(1, low, high);
    cnt1 = ( ((uint64)high)<<32 ) | low;
    /*keep only low 40bits, but this seems unnecessary as pmc ony read 8bits into high */
    cnt1 &= 0xffffffffff;  

    rdtsc(low, high);    
    ccnt = ( ((uint64)high)<<32 ) | low;

    if(type==ENTER)
      {
	/* record the entry values of the counters */
	enter_cnt0 = cnt0;
	enter_cnt1 = cnt1;
	enter_ccnt = ccnt;
      }
    else
      {
	if(samples_taken <  MAX_SAMPLES)
	  {
	    samples[samples_taken*MAX_ELEMENTS + 0] = cnt0 - enter_cnt0;
	    samples[samples_taken*MAX_ELEMENTS + 1] = cnt1 - enter_cnt1;
	    samples[samples_taken*MAX_ELEMENTS + 2] = ccnt - enter_ccnt;
	    samples[samples_taken*MAX_ELEMENTS + 3] = (unsigned long long) flag;
	    samples_taken++;
	  }
      }
#endif

    return(0);    
}


int sys_readpmu(counters_t *pointer)
{
    uint32 low, high;
    uint64 cnt0, cnt1, ccnt;

    rdpmc(0, low, high);
    cnt0 = ( ((uint64)high)<<32 ) | low;
    /*keep only low 40bits, but this seems unnecessary as pmc ony read 8bits into high */
    cnt0 &= 0xffffffffff;  

    rdpmc(1, low, high);
    cnt1 = ( ((uint64)high)<<32 ) | low;
    /*keep only low 40bits, but this seems unnecessary as pmc ony read 8bits into high */
    cnt1 &= 0xffffffffff;  

    rdtsc(low, high);    
    ccnt = ( ((uint64)high)<<32 ) | low;

    /* record the entry values of the counters */
    pointer->counter0 = cnt0;
    pointer->counter1 = cnt1;
    pointer->clock = ccnt;

    return(0);    
}

int sys_wr_msr_32(uint32 msr, uint32 val)
{
    uint32 low, high;
    rdmsr(msr, low, high);
    wrmsr(msr,val, high);

    return(0);
}

int sys_wr_msr_40(uint32 msr, int val)
{
    uint32 low, high;
    rdmsr(msr,low,high);

    /* according to Intel IA-32 manual, Volume-3, pp15-67, only the low32bits
     * can be written. The bit32-39 will be sign extended according to the value
     * of bit 31. So we might only need to write the low32bits part. 
     * To be safe, I still clear out the 32-39 bits before writing */
    high &= ~0xff;  /*zero out low 8 bits*/
    wrmsr(msr, val, high);
    
    return(0);
}

int sys_rd_msr(uint32 msr, unsigned long *val)
{
    uint32 low, high;
    rdmsr(msr,low,high);
    
    // data = (((unsigned long long)high << 32) | (unsigned long long )low);

    *val = low;
    
    return(0);
}


/* Modify one of the low bits
 * val can only be 0 or 1
 * bit_num is 0~31
 */
int sys_wr_msr_bit(uint32 msr, uint32 bit_num, uint32 val)
{
    uint32 low, high;
    rdmsr(msr, low, high);
    
    //FIXME: check bit_num range
    if(val == 1) {
	low |= (1<<bit_num);
    } else if(val == 0) {
	low &= ~(1<<bit_num);
    } else {
      //	printk("Error: write_msr_low_bit_val, val must be 0 or 1\n");
	return(-ENODEV);
    }

    wrmsr(msr,low,high);
    return(0);
}


int sys_copy_sample_log(unsigned long long ** user_log, int * num_samples)/* CANO-2005 */
{
    int sample_no, read_samples;
    char msg_str[100]; 

    read_samples = current_sample_no;
    
    if(num_samples==NULL)
    {
	sprintf(msg_str,"\n[copy_sample_log]: Error: num samples is null!!\n");
	print_toTTY(msg_str);
	return(-1);
    }
    /* copy num_samples to usr */
    *num_samples = read_samples;

    if(user_log==NULL)
    {
	sprintf(msg_str,"\n[copy_sample_log]: Error: user log is null!!\n");
	print_toTTY(msg_str);
	return(-1);
    }
    /* record the samples */
    for (sample_no=0; sample_no<read_samples; sample_no++)
    {
      user_log[sample_no][CNTR0_ind]        = kernel_samples[sample_no][CNTR0_ind];
      user_log[sample_no][CNTR1_ind]        = kernel_samples[sample_no][CNTR1_ind];
      user_log[sample_no][TSC_ind]          = kernel_samples[sample_no][TSC_ind];
      user_log[sample_no][real_PHASE_ind]   = kernel_samples[sample_no][real_PHASE_ind];
      user_log[sample_no][pred_PHASE_ind]   = kernel_samples[sample_no][pred_PHASE_ind];

    }
    
    return(0);    
}

/* This will enable/disable kernel logging of PMUs */
/* 0--> off, 1--> on */
int sys_kernel_PMU_log_on_off(int on_off)/* CANO-2005 */
{
  char msg_str[100]; 

  if ( on_off == 0)
  {
    PMU_logging = 0;
    sprintf(msg_str,"\n[kernel PMU logging off]\n");
    print_toTTY(msg_str);
  }
  else if ( on_off == 1)
  {
    /* reset sample counter at the beginning of logging*/
    current_sample_no = 0;

    PMU_logging = 1;
    sprintf(msg_str,"\n[kernel PMU logging on]\n");
    print_toTTY(msg_str);
  }

  return(0);
}

int sys_set_delta_ins(int delta_ins_val)/* CANO-2005 */
{
  sampling_delta_ins = delta_ins_val;
  
  return(0);
}

/*****************************************************/
/* DVFS SYSCALLS */
/*****************************************************/

/* This one will set the (f,V) */
int sys_set_target_f(unsigned freq)
{
  unsigned int msr, oldmsr, high;
  char msg_str[100]; /* At most 100 char message */  

  /* THESE ARE THE DEVBOARD SETTINGS: */
  if (machine == DEVBOARD)
  {
    switch (freq) 
    {
      case 600: /* 600MHz*/
	msr = MSRVALUE(600,956);
	break;
      case 800: /* 800MHz*/
	msr = MSRVALUE(800,1036);
	break;
      case 1000: /* 1000MHz*/
	msr = MSRVALUE(1000,1164);
	break;
      case 1200: /* 1200MHz*/
	msr = MSRVALUE(1200,1276);
	break;
      case 1400: /* 1400MHz*/
	msr = MSRVALUE(1400,1420);
	break;
      case 1600: /* 1600MHz*/
	msr = MSRVALUE(1600,1484);
	break;
      default:
	sprintf(msg_str,"Invalid frequency target value: %d\n",freq);
	print_toTTY(msg_str);
	return (-1); /* this will be returned to the original call? */
    }
  }
  else if (machine == LAPTOP)
  {
    switch (freq) 
    {
      case 600: /* 600MHz*/
	msr = MSRVALUE(600,956);
	break;
      case 800: /* 800MHz*/
	msr = MSRVALUE(800,1116);
	break;
      case 1000: /* 1000MHz*/
	msr = MSRVALUE(1000,1228);
	break;
      case 1200: /* 1200MHz*/
	msr = MSRVALUE(1200,1356);
	break;
      case 1400: /* 1400MHz*/
	msr = MSRVALUE(1400,1452);
	break;
      case 1500: /* 1500MHz*/
	msr = MSRVALUE(1500,1484);
	break;
      default:
	sprintf(msg_str,"Invalid frequency target value: %d\n",freq);
	print_toTTY(msg_str);
	return (-1); /* this will be returned to the original call? */
    }
  }
  else
  {
	sprintf(msg_str,"Unknown Machine Type %d\n",machine);
	print_toTTY(msg_str);
	return (-1); /* this will be returned to the original call? */
  }
  
  /* check what the old target is */
  rdmsr(MSR_IA32_PERF_CTL, oldmsr, high);
  
  if((msr &0xffff) == (oldmsr & 0xffff)) /* the same target */
    return(0);
  
  /* if different target, write the new target */
  oldmsr &= ~0xffff; /* zeros out the low 16 bits */
  msr &= 0xffff;  /* just to be sure */
  oldmsr |= msr;  /* combine it into 32 bits */
  wrmsr(MSR_IA32_PERF_CTL, oldmsr, high);

  return(0);

}  
/**************************************************************/

/* Return current running (v,f) in (mV, MHz) */
static int sys_get_current_f_V(unsigned int * f, unsigned int * V)
{
  unsigned int low, high;

  rdmsr(MSR_IA32_PERF_STATUS, low, high);
  *f = MSR2MHZ(low);
  *V = MSR2MV(low);

  return(0);
}

/**************************************************************/

/* check back our set (v,f) in (mV, MHz) */
static int sys_get_target_f_V(unsigned int * f, unsigned int * V)
{
  unsigned int low, high;

  rdmsr(MSR_IA32_PERF_CTL, low, high);
  *f = MSR2MHZ(low);
  *V = MSR2MV(low);

  return(0);
}

/**************************************************************/

/* write to PARAPORT as specified by user */
static int sys_write_paraport(char pport_val)
{
  outb(pport_val, PARALLEL_PORT_ADDR);
  
  return(0);
}

/**************************************************************/

/* User program chooses predn type with this syscall */
int sys_choose_predn_type(int predict_type)/* CANO-2005 */
{
  char msg_str[100];
  
  if ((predict_type < NUM_PREDN) && (predict_type >= 0))
  {
    PREDN_TYPE = predict_type;
  }
  else
  {
    sprintf(msg_str,"\n[sys_choose_predn_type]: UNSUPPORTED PREDICTION TYPE: %d\n", predict_type);
    print_toTTY(msg_str);
    printk(msg_str);
    sprintf(msg_str,"I'll use Mem_GPHT instead!!\n");
    print_toTTY(msg_str);
    printk(msg_str);
    PREDN_TYPE = Mem_GPHT;
  }
  
  return(0);
}

/**************************************************************/
/**************************************************************/


void print_toTTY(char *str)
{
     struct tty_struct *my_tty;

     /* tty struct went into signal struct in 2.6.6 */
     #if ( LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,5) )
	/* The tty for the current task  */
	my_tty = current->tty;
     #else
	/* The tty for the current task, for 2.6.6+ kernels */
	my_tty = current->signal->tty;
     #endif 
 
     if (my_tty != NULL) 
     {
	((my_tty->driver)->write) (my_tty,	/* The tty itself */
        #if ( LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,9) )		
				   0,	/* Don't take the string from user space        */
        #endif
	                           str,	/* String                 */
				   strlen(str));	/* Length */
        #if ( LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,9) )		
	  ((my_tty->driver)->write) (my_tty, 0, "\015\012", 2);
	#else
	  ((my_tty->driver)->write) (my_tty, "\015\012", 2);
	#endif
     }
}




#define LOCAL_PERF_VECTOR 0xee

static int initialize_module(void)
{
  int i=0, j=0;
  unsigned low, high; /* low 32 and high 32 bits*/
  char msg_str[100]; /* At most 100 char message */  

  /* check if DVS is enabled. If no, enable it */
  rdmsr(MSR_IA32_MISC_ENABLE, low, high);
  if( (low & (1<<16)) ) /* enabled */
  {
    sprintf(msg_str,"\n\tAlriighht! SpeedStep Already Enabled (16th Bit of MSR_IA32_MISC_ENABLE = 1)\n");
    print_toTTY(msg_str);
    printk(msg_str);
  }
  else /* not enabled */
  { 
    sprintf(msg_str,"\n\tSpeedStep Not Enabled (16th Bit of MSR_IA32_MISC_ENABLE = 0)\n");
    print_toTTY(msg_str);
    sprintf(msg_str,"\n\tI'll try to enable SpeedStep for you (Set 16th Bit of MSR_IA32_MISC_ENABLE -> 1)\n");
    print_toTTY(msg_str);
    low |= (1<<16);
    wrmsr(MSR_IA32_MISC_ENABLE, low, high);

    /* check  if it is really enabled */
    rdmsr(MSR_IA32_MISC_ENABLE, low, high);
    if( !(low & (1<<16)) ) /* still not  enabled */
    {
      sprintf(msg_str,"\n\t! Could not enable Enhanced SpeedStep (DVS) for Pentium M. Bailing Out! \
                       \n\t(Couldn't Set 16th Bit of MSR_IA32_MISC_ENABLE -> 1)\n");
      print_toTTY(msg_str);
    
      sprintf(msg_str, "Module %s failed in Init!\n", MODULE_NAME);
      print_toTTY(msg_str);
      printk(msg_str);
      return(-ENODEV);
    }
  }

  /* Save old ...*/
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_readpmu];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_wr_msr_32];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_wr_msr_40];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_wr_msr_bit];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_rd_msr];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_flagComponent];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_copy_sample_log];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_kernel_PMU_log_on_off];
  old_refs[i++] = sys_call_table[SYSCALL_NO_sys_set_delta_ins];

  i=0;
  DVFS_old_refs[i++] = sys_call_table[SYSCALL_NO_sys_set_target_f];
  DVFS_old_refs[i++] = sys_call_table[SYSCALL_NO_sys_get_current_f_V];
  DVFS_old_refs[i++] = sys_call_table[SYSCALL_NO_sys_get_target_f_V];
  DVFS_old_refs[i++] = sys_call_table[SYSCALL_NO_sys_write_paraport];
  DVFS_old_refs[i++] = sys_call_table[SYSCALL_NO_sys_choose_predn_type];
  

  /* Copy new...*/
  sys_call_table[SYSCALL_NO_sys_readpmu]    = sys_readpmu;
  sys_call_table[SYSCALL_NO_sys_wr_msr_32]  = sys_wr_msr_32;
  sys_call_table[SYSCALL_NO_sys_wr_msr_40]  = sys_wr_msr_40;
  sys_call_table[SYSCALL_NO_sys_wr_msr_bit] = sys_wr_msr_bit;
  sys_call_table[SYSCALL_NO_sys_rd_msr]     = sys_rd_msr;
  sys_call_table[SYSCALL_NO_sys_flagComponent]  = sys_flagComponent;
  sys_call_table[SYSCALL_NO_sys_copy_sample_log]  = sys_copy_sample_log;
  sys_call_table[SYSCALL_NO_sys_kernel_PMU_log_on_off]  = sys_kernel_PMU_log_on_off;
  sys_call_table[SYSCALL_NO_sys_set_delta_ins]  = sys_set_delta_ins;

  sys_call_table[SYSCALL_NO_sys_set_target_f]        = sys_set_target_f;
  sys_call_table[SYSCALL_NO_sys_get_current_f_V]     = sys_get_current_f_V;
  sys_call_table[SYSCALL_NO_sys_get_target_f_V]      = sys_get_target_f_V;
  sys_call_table[SYSCALL_NO_sys_write_paraport]      = sys_write_paraport;
  sys_call_table[SYSCALL_NO_sys_choose_predn_type]   = sys_choose_predn_type;

  old_perf_handler = perf_handler;
  old_thermal_handler = thermal_handler;
  
  perf_handler = new_perf_handler;
  thermal_handler = new_thermal_handler;

  enable_thermal_interrupt();
  
  /* initialize GPHT state */
  for (i=0; i<PHT_entries; i++)
  {
	  for (j=0; j<GPHR_depth; j++)
	  {
		  PHT_tag[i][j] = 6;
	  }
	  PHT_pred[i][0] = 6;
	  PHT_age[i][0] = -1;
  }
  for (j=0; j<GPHR_depth; j++)
  {
	  GPHR[j] = 6;
  }
  
  /* INITIALIZE GLOBAL STATE FOR CONVERTING PHASE to DVFS FREQ SETTING */
  Phase2FREQmsr[0] = MSRVALUE(1500,1484) & 0x0ffff; /* DVFS Setting for Phase 1 */
  Phase2FREQmsr[1] = MSRVALUE(1400,1452) & 0x0ffff; /* DVFS Setting for Phase 2 */
  Phase2FREQmsr[2] = MSRVALUE(1200,1356) & 0x0ffff; /* DVFS Setting for Phase 3 */
  Phase2FREQmsr[3] = MSRVALUE(1000,1228) & 0x0ffff; /* DVFS Setting for Phase 4 */
  Phase2FREQmsr[4] = MSRVALUE(800,1116)  & 0x0ffff; /* DVFS Setting for Phase 5 */
  Phase2FREQmsr[5] = MSRVALUE(600,956)   & 0x0ffff; /* DVFS Setting for Phase 6 */


  sprintf(msg_str,"\tModule %s loaded.\n",MODULE_NAME);
  print_toTTY(msg_str);
  printk(msg_str);

  return 0;
}

static void unload_module(void)
{
  int i=0;
  char msg_str[100]; /* At most 100 char message */  
  
  /* Restore old... */ 
  sys_call_table[SYSCALL_NO_sys_readpmu]        = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_wr_msr_32]      = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_wr_msr_40]      = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_wr_msr_bit]     = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_rd_msr]         = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_flagComponent]  = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_copy_sample_log]  = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_kernel_PMU_log_on_off]  = old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_set_delta_ins]  = old_refs[i++];

  i=0;  
  sys_call_table[SYSCALL_NO_sys_set_target_f]        = DVFS_old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_get_current_f_V]     = DVFS_old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_get_target_f_V]      = DVFS_old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_write_paraport]      = DVFS_old_refs[i++];
  sys_call_table[SYSCALL_NO_sys_choose_predn_type]   = DVFS_old_refs[i++];
  
  perf_handler = old_perf_handler;
  thermal_handler = old_thermal_handler;

  sprintf(msg_str,"\n\tModule %s UNloaded.\n",MODULE_NAME);
  print_toTTY(msg_str);
  printk(msg_str);

}



module_init(initialize_module);
module_exit(unload_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gilberto & Canturk");
MODULE_DESCRIPTION("Perf Module");
