/*
 * pfmon_util_mips64.c  - MIPS64 specific set of helper functions
 *
 * Contributed by Philip Mucci <mucci@cs.utk.edu>
 *
 * This file is part of pfmon, a sample tool to measure performance 
 * of applications on Linux.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307 USA
 */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <unistd.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/time.h>

#include "pfmon.h"

typedef struct bp_save {
  unsigned long address;
  unsigned long val;
  int is_start;
} breakpoint_t;

#define BP 0x0005000d  /* break opcode */

/*
 * NPTL:
 * 	getpid() identical for all thread
 * 	gettid() unique for each thread
 * LinuxThreads:
 * 	getpid() unique for each thread
 *	gettid() undefined by library, could be getpid()
 *
 * To avoid issues between NPTL and LinuxThreads, we hardcode gettid() 
 * to always return the value managed by the kernel.
 *
 * Kernel (independent of thread package):
 * 	sys_gettid(): kernel task->pid
 * 	sys_getpid(): kernel task->tgid
 * 	first task in group is such that task->tgid == task->pid
 */
pid_t
gettid(void)
{
#ifndef __NR_gettid
#define __NR_gettid 186
#endif
  return (pid_t)syscall(__NR_gettid);
}

breakpoint_t bpoints[2] = { { 0, 0, 0 } ,{ 0, 0, 0 } } ;

static int
pfmon_mips64_set_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  long r;
  long val = 0;
  unsigned long bpi;
#if __BYTE_ORDER == __LITTLE_ENDIAN
  bpi = BP;
  bpi |= val & 0xffffffff00000000;
#elif __BYTE_ORDER == __BIG_ENDIAN
  bpi = ((unsigned long)BP) << 32;
  bpi |= val & 0x00000000ffffffff;
#else
#error "cannot determine endianess"
#endif

  vbprintf("[%d] setting code breakpoint DB%d, START%d @%p\n", 
	   pid, dbreg, is_start,
	   address);
  if (address == 0)
    {
      warning("address of 0 to set breakpoint\n", pid, strerror(errno));
      return -1;
    }

  val = ptrace(PTRACE_PEEKTEXT, pid, address, 0);
  if ((val == -1) && (errno)) {
    warning("set cannot peek[%d] %s\n", pid, strerror(errno));
    return -1;
  }

  DPRINT(("DB%d START%d curr instruction = 0x%016lx at 0x%lx\n", dbreg, is_start, val, address));
  if (val == bpi)
    {
      warning("breakpoint already set at 0x%lx\n", address);
      return 0;
    }

  r = ptrace(PTRACE_POKETEXT, pid, address, bpi);
  if (r == -1) {
    warning("set cannot poke[%d] %s\n", pid, strerror(errno));
    return -1;
  }

  bpoints[dbreg].address = address;
  bpoints[dbreg].val = val;
  bpoints[dbreg].is_start = is_start;

  r = ptrace(PTRACE_PEEKTEXT, pid, address, 0);
  if ((r == -1) && (errno)) {
    warning("clear cannot peek[%d] %s\n",pid,strerror(errno));
  } else {
    DPRINT(("new instruction = 0x%016lx at 0x%lx\n", r, address)); }

  return 0;
}

/*
 * common function to clear a breakpoint
 */
static int
pfmon_mips64_clear_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  unsigned long offs, tmp, val;
  long r;

  vbprintf("[%d] clearing code breakpoint DB%d, START%d, @%p\n", 
	   pid,dbreg,is_start,
	   address);

  r = ptrace(PTRACE_PEEKTEXT, pid, address, 0);
  if ((r == -1) && (errno)) {
    warning("clear cannot peek %s\n",strerror(errno));
  }

  val = bpoints[dbreg].val;
  /* bpoints[dbreg].val = 0; 
  bpoints[dbreg].address = 0; 
  bpoints[dbreg].is_start = 0; */
  
  DPRINT(("DB%d START%d curr instruction = 0x%016lx at 0x%lx, writing 0x%016lx\n",dbreg,is_start,r,address,val));
  
  r = ptrace(PTRACE_POKETEXT, pid, address, val);
  if (r == -1) {
    warning("clear cannot poke %s\n", strerror(errno));
    return -1;
  }

  r = ptrace(PTRACE_PEEKTEXT, pid, address, 0);
  if ((r == -1) && (errno)) {
    warning("clear cannot peek %s\n",strerror(errno));
  } else {
    DPRINT(("new instruction = 0x%016lx at 0x%lx\n", r, address)); }

  offs = EF_CP0_EPC;

  DPRINT(("Setting PC back to 0x%lx\n", address));

  tmp  = (unsigned long)ptrace(PTRACE_POKEUSER, pid, offs, address);
  if (tmp == (unsigned long)-1) {
    warning("cannot poke PC: %s\n", strerror(errno));
    return -1;
  }

  return 0;
}

/*
 * this function sets a code breakpoint at bundle address
 * In our context, we only support this features from user level code (of course). It is 
 * not possible to set kernel level breakpoints.
 *
 * The dbreg argument varies from 0 to 3, dr7, dr6 are not visible.
 */
int
pfmon_set_code_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  if (dbreg < 0 || dbreg >= options.nibrs) return -1;

  return pfmon_mips64_set_breakpoint(pid, dbreg, address, is_start);
}

int
pfmon_clear_code_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  if (dbreg < 0 || dbreg >= options.nibrs) return -1;

  return pfmon_mips64_clear_breakpoint(pid, dbreg, address, is_start);
}

int
pfmon_set_data_breakpoint(pid_t pid, int dbreg, unsigned long address, int rw, int is_start)
{
  return -1;
}

int
pfmon_clear_data_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  return -1;
}

int
pfmon_resume_after_code_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  /* This clears the current breakpoint and restores the current instruction. */
  pfmon_mips64_clear_breakpoint(pid,dbreg,address,is_start);

  if (dbreg == 0)
    dbreg = 1;
  else
    dbreg = 0;

  if (is_start == 0)
    pfmon_mips64_set_breakpoint(pid,dbreg,bpoints[dbreg].address,is_start);

  return 0;
}

int
pfmon_resume_after_data_breakpoint(pid_t pid, int dbreg, unsigned long address, int is_start)
{
  return -1;
}

void
pfmon_arch_initialize(void)
{
  options.opt_support_gen = 0;
  options.libpfm_generic  = 0;

  /*
   * XXX: temporary hack. pfmon allows both code and data
   * triggers to be set at the same time. Yet there is only
   * one set of DB registers. We should really use an allocator
   * but for nwo split registers into two sets and hack a
   * base in the code
   */
  options.nibrs = 2;
  options.ndbrs = 0;
}

int
pfmon_enable_all_breakpoints(pid_t pid)
{
  return 0;
}

int
pfmon_disable_all_breakpoints(pid_t pid)
{
  return 0;
}

int
pfmon_validate_code_trigger_address(unsigned long addr)
{
  return 0;
}
	
int
pfmon_validate_data_trigger_address(unsigned long addr)
{
  return 0;
}

void
pfmon_segv_handler_info(struct siginfo *si, void *sc)
{
  struct ucontext *uc;
  unsigned long ip;
  uc = (struct ucontext *)sc;
  ip = uc->uc_mcontext.pc;
  printf("<pfmon fatal error @ [%d:%d] ip=0x%lx>\n", getpid(), gettid(), ip);
}

int
pfmon_get_breakpoint_addr(pid_t pid, unsigned long *addr, int *is_data)
{
  long tmp;
  void *offs = NULL;
  /* Here we should check if we are mips or mip64 kernel */
  offs = (void *)64;
  /* offs = (void *)EF_CP0_EPC; 32 bit kernel*/
  DPRINT(("Getting PC from PID %d at offset %ld\n",pid,offs));
#ifdef YUCK
  if (options.opt_debug) {
    unsigned long i;
    for (i=0;i<=EF_SIZE/4;i++)
      {
	DPRINT(("%lu 0x%lx\n",i,ptrace(PTRACE_PEEKUSER, pid, (void *)i, 0)));
	if (errno) break;
      }
  }
#endif
  tmp = ptrace(PTRACE_PEEKUSER, pid, offs, 0);
  if ((tmp == -1) && (errno)){
    warning("cannot PEEKUSER at offset %lu for PC[%d]: %s\n", (unsigned long)offs,pid,strerror(errno));
    return -1;
  }
  DPRINT((">>>>>%lu: PC address=0x%lx\n", offs,tmp));

  *addr = tmp;
  *is_data = 0;
  return 0;
}

int
pfmon_get_return_pointer(pid_t pid, unsigned long *rp)
{
  long tmp;
  unsigned long offs;

  offs = EF_REG31;

  tmp  = (unsigned long)ptrace(PTRACE_PEEKUSER, pid, offs, 0);
  if ((tmp == -1) && (errno)) {
    warning("cannot retrieve stack pointer at offset %ld: %s\n", offs, strerror(errno));
    return -1;
  }

  DPRINT((">>>>>return address=0x%lx\n", tmp));
  *rp = tmp;
  return 0;
}

/*
 * we define our own syscall entries because depending on the version
 * of glibc the affinity calls are supported with a different API.
 * In other words, there is a glibc interface that then maps onto
 * the kernel interface which has been stable for quite some time now.
 */
int
__pfmon_set_affinity(pid_t pid, size_t size, pfmon_bitmask_t mask)
{
#ifndef __NR_sched_setaffinity
#define __NR_sched_setaffinity 241
#endif
  return (int)syscall(__NR_sched_setaffinity, pid, size, mask);
}

int
__pfmon_get_affinity(pid_t pid, size_t size, pfmon_bitmask_t mask)
{
#ifndef __NR_sched_getaffinity
#define __NR_sched_getaffinity 242
#endif
  return (int)syscall(__NR_sched_getaffinity, pid, size, mask);
}

void
pfmon_print_simple_cpuinfo(FILE *fp, const char *msg)
{
  char *cpu_name, *p, *stepping;
  char *cache_str;
  size_t cache_size;
  int ret;

  ret = find_in_cpuinfo("dcache size\t\t", &cache_str);
  if (ret == -1)
    cache_str = strdup("0");

  /* size in KB */
  sscanf(cache_str, "%zu", &cache_size);

  ret = find_in_cpuinfo("cpu model", &cpu_name);
  if (ret == -1)
    cpu_name = strdup("unknown");

  /*
   * skip leading spaces
   */
  p = cpu_name;
  while (*p == ' ') p++;

  ret = find_in_cpuinfo("stepping", &stepping);
  if (ret == -1)
    stepping = strdup("??");

  fprintf(fp, "%s %lu-way %luMHz/%.2fMB -- %s (stepping %s)\n", 
	  msg ? msg : "", 
	  options.online_cpus, 
	  options.cpu_mhz,
	  (1.0*(double)cache_size)/(double)1024,
	  p, stepping);

  free(cache_str);
  free(cpu_name);
  free(stepping);
}

void
pfmon_print_cpuinfo(FILE *fp)
{
  /*
   * assume all CPUs are identical
   */
  pfmon_print_simple_cpuinfo(fp, "# host CPUs: ");
}
