/*
 * Copyright (C) 2000 TimeSys Corporation
 *
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */
#include <linux/module.h>
#include <linux/sys.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/measure.h>
#include <asm/uaccess.h>
#include <linux/string.h>

void bbbb(void) {}
static int measure_sys_process=-1;
static MEASURE measure_array[MEASURE_MAX_ENTRY];
static int measure_ptr=0;
static int measure_all=0;
static int measure_threshold=100;
static unsigned int measure_startirq;
static unsigned long long measure_lasttimestamp;
static char measure_sys[NR_syscalls] = {0};
static char measure_break_status[255]= {0};
static unsigned long measure_irq_time[16];
static unsigned long long  measure_irq_start[16];
static int measure_irq_count[16];
static char measure_irq[16]={0};
static unsigned long slice_seq=0;
static int measure_lastsysno=0;
static unsigned long measure_interrupt=0;
static int measure_monitor_irq=-1;
static int measure_monitor_pagefault=0;
static int measure_softirq=0;
static unsigned long long measure_softirq_start;
static int measure_schedule=0;
static unsigned long long measure_schedule_start;
static int measure_cs=0;
static unsigned long long measure_cs_start;

/* Measurement Options */
int isMeasureContextswap = 1;
int isMeasureNewProcess = 1;
int isMeasureEndProcess = 1;
static void add_user_event(int pid, MEASURE *m)
{
    measure_add_longentry(pid,MEASURE_USER_EVENT,m->flags,m->subseq,m->measure);
}

static int def_measure_register(int pid,int cmd,long data)
{
  MEASURE m;

  //printk("sys_measure_register(%d,%d,%d)\n",pid,cmd,data);
  // DSH Note: can this be done from RTAI?
  if (pid == -1) {
    pid = current->pid;
  }
  if (cmd == MEASURE_OFF) {
    current->sysno = -1;
    measure_sys_process = -1;
    measure_all = 0;
    measure_ptr = 0;
    memset(measure_sys,0,sizeof(measure_sys));
    memset(measure_break_status,0,sizeof(measure_break_status));
    return 0;
  } else if (cmd == MEASURE_ON) {
    measure_ptr = 0;
    measure_sys_process = data;
    measure_all = 1;
    measure_break_status[200] = measure_break_status[201] = 1;
    measure_break_status[202] = measure_break_status[203] = 1;
    return 0;
  } else if (cmd == MEASURE_REGISTER_SYS) {
    if (data < NR_syscalls && data >= 0) {
      //printk("Turn on system call %d\n",data);
      measure_sys[data] = 1;
      return 0;
    } else {
      return -ENOSYS;
    }
  } else if (cmd == MEASURE_UNREGISTER_SYS) {
    if (data < NR_syscalls && data >= 0) {
      measure_sys[data] = 0;
      return 0;
    } else {
      return -ENOSYS;
    }
  } else if (cmd == MEASURE_SET_BREAK) {
    if (data <= 255) {
      //printk("set break[%d] at %d\n",data,measure_break_status[data]);
      measure_break_status[data] = 1;
      return 0;
    } else {
      return -EINVAL;
    }
  } else if (cmd == MEASURE_CLEAR_BREAK) {
    if (data <= 255) {
      measure_break_status[data] = 0;
      return 0;
    } else
      return -EINVAL;
  } else if (cmd == MEASURE_THRESHOLD) {
    measure_threshold = data;
    return 0;
  } else if (cmd == MEASURE_SET_IRQ) {
    if ((data < 0) || (data >= 16))
      return -EINVAL;
    measure_irq[data]       = 1;
    measure_irq_time[data]  = 0;
    measure_irq_count[data] = 0;
    return 0;
  } else if (cmd == MEASURE_CLEAR_IRQ) {
    if ((data < 0) || (data >= 16))
      return -EINVAL;
    measure_irq[data] = 0;
    return 0;
  } else if (cmd == MEASURE_MONITOR_INTERRUPT) {
    measure_monitor_irq = data;
    return 0;
  } else if (cmd == MEASURE_MONITOR_PAGEFAULT) {
    measure_monitor_pagefault = data;
    return 0;
  } else if (cmd == MEASURE_GET_IRQ) {
    if (measure_irq_count[data] == 0)
        return 0;
    else
        return measure_irq_time[data]/measure_irq_count[data];
  } else if (cmd == MEASURE_MONITOR_SOFTIRQ) {
    measure_softirq = data;
    return 0;
  } else if (cmd == MEASURE_MONITOR_SCHEDULE) {
    measure_schedule = data;
    return 0;
  } else if (cmd == MEASURE_MONITOR_CS) {
    measure_cs = data;
    return 0;
  } else if (cmd == MEASURE_MONITOR_CONTEXTSWAP) {
    isMeasureContextswap = data? 1:0;
    printk("isMeasureContextswap is set to %d\n",isMeasureContextswap);
  } else if (cmd == MEASURE_MONITOR_NEWPROCESS) {
    isMeasureNewProcess = data? 1:0;
  } else if (cmd == MEASURE_MONITOR_ENDPROCESS) {
    isMeasureEndProcess = data? 1:0;
  } else if (cmd == MEASURE_USER_EVENT) {
    copy_from_user(&m,(MEASURE*)data,sizeof(m));
    add_user_event(pid,&m);
  } else {
    return -EINVAL;
  }
  return 0;
}

static int def_measure_getinfo(MEASURE *info)
{
  int ret;

  if (measure_ptr == 0)
    return 0;

  copy_to_user(info,measure_array,measure_ptr*sizeof(MEASURE));
  ret = measure_ptr;
  measure_ptr = 0;
  //printk("return %d entries\n",ret);
  return ret;
}


static void def_measure_add_entry(unsigned int pid,
                                  unsigned char flags,
				  unsigned int sysno,
				  unsigned char subseq,
				  unsigned int usec)
{
  //if (pid == 0) return;
  //if (sysno == 255) return;
  if (measure_ptr >= MEASURE_MAX_ENTRY)
    return;
  //if (usec > 900*1000) return;
  if (usec > measure_threshold) {
    measure_array[measure_ptr].pid    = pid;
    measure_array[measure_ptr].flags  = flags;
    measure_array[measure_ptr].sysno  = sysno;
    measure_array[measure_ptr].subseq = subseq;
    measure_array[measure_ptr++].measure = usec;
  }
}

static void def_measure_add_longentry(int pid,
                                      unsigned char flags,
				      int sysno,
				      unsigned char subseq,
				      unsigned long long usec)
{
  //if (pid == 0) return;
  //if (sysno == 255) return;
  if (measure_ptr >= MEASURE_MAX_ENTRY)
    return;
  measure_array[measure_ptr].pid    = pid;
  measure_array[measure_ptr].flags  = flags;
  measure_array[measure_ptr].sysno  = sysno;
  measure_array[measure_ptr].subseq = subseq;
  measure_array[measure_ptr++].measure = usec;
}


static void def_measure_begin(int no)
{
  unsigned long long now;
  
  measure_lastsysno = no;
  if (measure_all||(current->pid != measure_sys_process)) {
    if (current->pid == 0) return;
    if ((no < 0) || (no >= NR_syscalls)) return;
  
    if (measure_sys[no]==0) return;
    //printk("measure_begin %d\n",no);
    current->sysno = no;
    now = get_rdtsc();
    measure_lasttimestamp = now;
    def_measure_add_longentry(current->pid,MEASURE_SYSCALL,current->sysno,0,now);
  }
}


static void def_measure_end(void)
{
  unsigned long long now;
  unsigned long long usec;

  measure_lastsysno = 0;
  /* We use measure_head to determine if measurement is to be start*/
  if (current->sysno != -1) {
    now = get_rdtsc();
    //printk("measure_end\n");
    usec = now-measure_lasttimestamp;
    def_measure_add_longentry(current->pid,MEASURE_END,current->sysno,0,usec);
    current->sysno = -1;
  }
  slice_seq++;
}

static void def_measure_break(int mark)
{
  unsigned long long now;
  unsigned long long usec=0;

  if (current->sysno != -1) {
    if (measure_break_status[mark] == 0)
      return;
    now = get_rdtsc();
    usec = now - measure_lasttimestamp;
    if (measure_ptr < MEASURE_MAX_ENTRY-1) {
      def_measure_add_longentry(current->pid,MEASURE_SLICE,current->sysno,mark,usec);
      //printk("mark %d %d\n",mark,(int)measure_array[measure_head].header.subseq);
    }
    measure_lasttimestamp = now;
  }
  slice_seq++;
}

static void def_measure_irq_on(int irq)
{
  if (current->sysno != -1) {
    def_measure_break(200);
  }
  if (measure_irq[irq]) {
    measure_irq_start[irq] = get_rdtsc();
  }
}

static void def_measure_irq_off(int irq)
{
  unsigned long long finish,usec;
  

  if (current->sysno != -1) {
    def_measure_break(201);
  }
  if (measure_irq[irq]) {
    finish = get_rdtsc();
    if (measure_irq_time[irq] > 0x2fffffff) {
      measure_irq_time[irq]<<=1;
      measure_irq_count[irq]<<=1;
    }
    usec = finish-measure_irq_start[irq];
    measure_irq_time[irq] += usec;
    measure_irq_count[irq]++;
    if (measure_monitor_irq)
      def_measure_add_longentry(-1,MEASURE_IRQ,irq,0,usec);
  }
}

static void def_measure_softirq_on(int intno)
{
  if (measure_softirq) {
    measure_softirq_start = get_rdtsc();
  }
}

static void def_measure_softirq_off(int no)
{
  unsigned long long usec, finish;

  if (measure_softirq) {
    finish = get_rdtsc();
    usec = finish-measure_softirq_start;
    def_measure_add_longentry(-1,MEASURE_SOFTIRQ,no,0,usec);
  }
}

static void def_measure_schedule_on(void)
{
  if (measure_schedule) {
    measure_schedule_start = get_rdtsc();
  }
}

static void def_measure_schedule_off(void)
{
  unsigned long long usec, finish;

  if (measure_schedule) {
    finish = get_rdtsc();
    usec = finish-measure_schedule_start;
    def_measure_add_longentry(-1,MEASURE_SD,0,0,usec);
  }
}

static void def_measure_cs_on(void)
{
  if (measure_cs) {
    measure_cs_start=get_rdtsc();
  }
}

static void def_measure_cs_off(void)
{
  unsigned long long finish, usec;

  finish = get_rdtsc();
  if (measure_cs) {
    usec = finish-measure_cs_start;
    def_measure_add_longentry(-1,MEASURE_CS,0,0,usec);
  }
  def_measure_add_longentry(current->pid,MEASURE_CONTEXTSWAP_TO,0,0,finish);
}

static void def_measure_end_slice(int pid)
{
  if (((current->pid != measure_sys_process)&&(current->sysno != -1))
       || measure_all) {
    unsigned long long now,usec;
    
    now = get_rdtsc();
    usec = now - measure_lasttimestamp;

    if (measure_ptr < MEASURE_MAX_ENTRY) {
      def_measure_add_longentry(current->pid,MEASURE_SLICE,current->sysno,0,usec);
    }
  }
  slice_seq++;
}

static void def_measure_new_slice(int pid)
{
  if (((current->pid != measure_sys_process)&&(current->sysno != -1)) 
       || measure_all) {
    unsigned long long now;
    
    now = get_rdtsc();
    //printk("%d...",now.tv_usec);
    measure_lasttimestamp = now;
  }
}

static void def_measure_clear_process(int pid)
{
  if (measure_sys_process != current->pid) {
    measure_all = 0;
    measure_ptr = 0;
    current->sysno = -1;
    measure_sys_process = -1;
    memset(measure_sys,0,sizeof(measure_sys));
  }
}

static void def_measure_contextswap(int pid)
{
  if (isMeasureContextswap)
	measure_add_longentry(pid,MEASURE_CONTEXTSWAP_TO,0,0,get_rdtsc());
}

static void measure_add_string(char *s)
{
  if (measure_ptr < MEASURE_MAX_ENTRY) {
    strncpy(&measure_array[measure_ptr],s,sizeof(MEASURE));
    measure_ptr++;
  }
}

static void def_measure_newprocess(int pid)
{
  char buf[15];
  char c;
  struct task_struct *task = current;
  char *name,*cname;

  // find the task from its pid
  do {
    if (task->pid == pid) break;
    task = task->next_task;
  } while(task != current);
  if (task->pid != pid) {
    // Can't find this task. It's wired!!!
    buf[0] = 0;
  }
  name = task->comm;
  cname = buf;
  while(*name) {
    *cname = *name;
    if (*name == '\\') {
      *cname=name[1];
      name +=2;
      cname+=2;
    } else if (*name == '\n') {
      *cname++='\\';
      *cname++='n';
      name++;
    } else {
      cname++;
      name++;
    }
   if (cname - buf > 14) break;
  }
  *cname = 0;
  if (isMeasureNewProcess) {
	measure_add_longentry(pid,MEASURE_NEWPROCESS,0,0,get_rdtsc());
        measure_add_string(buf);
  }
}

static void def_measure_endprocess(int pid)
{
  if (isMeasureEndProcess)
	measure_add_longentry(pid,MEASURE_ENDPROCESS,0,0,get_rdtsc());
}

static void def_measure_syscall(int pid,int no)
{
  if ((no < 0) || (no > NR_syscalls)) return;
  if (measure_sys[no]) 
    measure_add_longentry(pid,MEASURE_SYSCALL,no,0,get_rdtsc());
}

static void def_measure_new_reserve(cpu_reserve_t cpu)
{
}

static void def_measure_end_reserve(cpu_reserve_t cpu)
{
}

static void def_measure_rsv_swapto(rk_reserve_t rsv)
{
}

static void def_measure_rsv_att_proc(rk_reserve_t rsv, int pid)
{
}

static void def_measure_rsv_det_proc(rk_reserve_t rsv, int pid)
{
}

#if 0
MeasureOp measure_op = {
  def_measure_register,
  def_measure_getinfo,
  def_measure_add_entry,
  def_measure_add_longentry,
  def_measure_begin,
  def_measure_end,
  def_measure_break,
  def_measure_irq_on,
  def_measure_irq_off,
  def_measure_softirq_on,
  def_measure_softirq_off,
  def_measure_schedule_on,
  def_measure_schedule_off,
  def_measure_cs_on,
  def_measure_cs_off,
  def_measure_new_slice,
  def_measure_end_slice,
  def_measure_clear_process,
};
#else
MeasureOp measure_op = {
  def_measure_register,			/*def_measure_register*/
  def_measure_getinfo,			/*def_measure_getinfo*/
  def_measure_add_entry,		/*def_measure_add_entry*/
  def_measure_add_longentry,		/*def_measure_add_longentry*/
  def_measure_begin,			/*def_measure_begin,*/
  NULL,					/*def_measure_end,*/
  NULL,					/*def_measure_break,*/
  NULL,					/*def_measure_irq_on,*/
  NULL,					/*def_measure_irq_off,*/
  NULL,					/*def_measure_softirq_on,*/
  NULL,					/*def_measure_softirq_off,*/
  NULL,					/*def_measure_schedule_on,*/
  NULL,					/*def_measure_schedule_off,*/
  NULL,//def_measure_cs_on,			/*def_measure_cs_on,*/
  NULL,//def_measure_cs_off,			/*def_measure_cs_off,*/
  NULL,					/*def_measure_new_slice,*/
  NULL,					/*def_measure_end_slice,*/
  NULL,					/*def_measure_clear_process*/
  def_measure_contextswap,		/*def_measure_contextswap*/
  def_measure_newprocess,		/*def_measure_newprocess*/
  def_measure_endprocess,		/*def_measure_endprocess*/
  def_measure_syscall,			/*def_measure_syscall*/
  def_measure_new_reserve,
  def_measure_end_reserve,
  def_measure_rsv_att_proc,
  def_measure_rsv_det_proc,
  def_measure_rsv_swapto
};
#endif

extern void *sys_call_table[];

static int sys_measure_register(int pid,int cmd,int data)
{
  if (current_measure_hook&&current_measure_hook->reg)
    return current_measure_hook->reg(pid,cmd,data);
  return -ENOSYS;
}

static int sys_measure_getinfo(MEASURE *info)
{
  if (current_measure_hook&&current_measure_hook->getinfo)
    return current_measure_hook->getinfo(info);
  return -ENOSYS;
}


int init_module(void)
{
	if (current_measure_hook)
		return -EBUSY;
	current_measure_hook = &measure_op;

	/* Insert system calls */
	if (sys_call_table[400]!=sys_call_table[0] || 
		 sys_call_table[401]!=sys_call_table[0]) {
		printk(KERN_ERR "Measurement system calls already in use.\n");
		return -EBUSY;
	}
	sys_call_table[400]=sys_measure_register;
	sys_call_table[401]=sys_measure_getinfo;

	
	printk("loaded at %p\n",bbbb);
	return 0;
}

void cleanup_module(void)
{
	sys_call_table[400]=sys_call_table[401]=sys_call_table[0];
	current_measure_hook = NULL;
}

#if 0
static int syscall_read(char *page, char **start, off_t off, int count, int *eof, void *data);
static int break_stat(char *page, char **start, off_t off, int count, int *eof, void *data);

void init_measure()
{
  struct proc_dir_entry *proc_dir, *proc_sys, *proc_break, *proc_th;

  proc_dir = create_proc_entry("measure",S_IFDIR,0);
  proc_sys = create_proc_entry("syscall",S_IFREG|S_IRUGO,proc_dir);
  proc_sys->read_proc = syscall_read;

  proc_break = create_proc_entry("break",S_IREG|S_IRUGO,proc_dir);
  proc_break->read_proc = break_read;

  proc_th = create_proc_entry("break",S_IREG|S_IRUGO,proc_dir);
  proc_th->read_proc = syscall_read;
}

static int syscall_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
  char *p = page;
  for(i=0;i<NR_syscalls;i++) {
    if (measure_sys[i])
      p += sprintf(p,"%d ",i,measure_sys[i]);
  }
  p += sprintf(p,"\n");
  return p-page;
}


static int break_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
  char *p = page;
  for(i=0;i<255;i++) {
    if (measure_break_status[i])
      p += sprintf(p,"%d ",i,measure_break_status[i]);
  }
  p += sprintf(p,"\n");
  return p-page;
}

static int threshold_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
  char *p = page;
  p += sprintf(p,"%d",measure_threshold);
  return p-page;
}

#endif
