#include <stdio.h>
#include <sys/types.h>
#include <libcpc.h>
#include <errno.h>
#include <sys/processor.h>
#include <sys/procset.h>

/*
 * On SPARC we are using cycle counters in the shared mode, because most
 * benchmarks require timing events that happen outside of the context of
 * the current process, which is not possible to do with private counters.
 * Using counters in shared mode requires super-user privileges. Therefore, 
 * you must su root before running HBenchOS with hardware counters on 
 * Solaris/SPARC. This means that CMODE_SHARED has to be defined on SPARC/Solaris.
 *
 * The code that uses per-process counters is left in the files, in case it 
 * is decided at some point to use both modes. 
 */
////////////////////////////////////////////////////////////
int shared_fd;

#ifdef CMODE_SHARED
void
read_cycle_counter(uint64_t *res_p)
{
    cpc_event_t ce;
    
    if (cpc_shared_take_sample(shared_fd, &ce) == -1)
    {
	perror("cpc_shared_take_sample");
	exit(-1);
    }
    *res_p = ce.ce_pic[0];
    
}

#else
/* SPARC pic register holds 2 performance counters.
 * Because we are using gcc, which outputs 32-bit code, 
 * we can only read one counter at a time.
 * Some say that even though gcc things that registers 
 * are 32-bit, if the hardware has 64-bit registers, then
 * the value is, in fact, there. And we just have to 
 * extract the other 32 bits with the assembly code. 
 * When I tried this, as complained that it cannot shift by
 * 32 bits. Perhaps there is some special assembly instruction
 * to perform 32-bit shifts? I am not investigating this 
 * further at the moment, because I only need to add the 
 * support for hardware cycle counters. Therefore, the below
 * suffices.
 */

#define read_cycle_counter(res_p)   \
 __asm __volatile("rd\t%%pic, %%o0         \n" \
       :"=r" (*(uint64_t*)(res_p)):)  \

#endif
////////////////////////////////////////////////////////////
/*
 * Prints detailed error message for errors generated
 * by the cpc library.
 */
void
my_errfn(const char *fn, const char *fmt, va_list ap)
{
    (void) fprintf(stderr, "cpc_%s(): ", fn);
    (void) vfprintf(stderr, fmt, ap);
}

/* 
 * Initialize SPARC cycle counting.
 * The calling function does not expect this
 * to return error. So if we fail we just report this to the
 * user and proceed. (Should we really exit?)
 */
int
init_sparc_cycle_counters()
{
    int version;
    int cpuver;
    cpc_event_t event;
    processorid_t cur_proc = -1;


    /* This string tells the CPU which events to collect.
     * We only care about cycles here, but we do not know
     * how the CPU is going to react if we only tell it
     * to collect one counter. So we ask it to collect
     * instructions as well.
     */
    const char *setting = "pic0=Cycle_cnt,pic1=Instr_cnt";
    cpc_seterrfn(my_errfn);
  
    if ( (version=cpc_version(CPC_VER_CURRENT)) == CPC_VER_NONE) {
        /* version mismatch - library cannot translate */
        return -1;
    }  

    if(cpc_access())
    {
	return -1;
    }

    /* Get the CPU version */
    if( (cpuver = cpc_getcpuver()) == -1)
    {
        return -1;
    }

    /* Initialize the event structure */
    if (cpc_strtoevent(cpuver, setting, &event) != 0)
    {
        return -1;
    }
    else
    {
        setting = cpc_eventtostr(&event);
    }
    
    /* Tell the kernel that we want to measure this event.
     * If there is a possibility that the event counter
     * will overflow, we should pass a CPC_BIND_EMT_OVF flag
     * to this function, which will send our process a 
     * signal in an event the counter is overflown.
     * Note, not all CPUs support such flag. If they don't
     * then cpc_bind_event called with this flag will fail.
     */
#ifdef CMODE_SHARED
	
    if(processor_bind(P_PID, getpid(), 0, 0))
    {
	perror("processor_bind");
	printf(" failure\n");
	return -1;
    }

    /* Initialize access in shared mode */
    shared_fd = cpc_shared_open();
    if(shared_fd <= 0)
    {
	perror("cpc_shared_open()");
	return -1;
    }
    if(cpc_shared_bind_event(shared_fd, &event,
			     0/*CPC_BIND_LWP_INHERIT */ ))
    {
	return -1;
    }

#else
    if(cpc_bind_event(&event, 0))
    {
        return -1;
    }
#endif

    if(cpc_count_sys_events(1) || cpc_count_usr_events(1))
    {
	return -1;
    }

    return 0;
}
