/** 
 * This is the native code that supports the timer manager implementation
 *
 */

#include "config.h"
#include "util.h"
#include "timer.h"
#include "eventmanager.h"
#include "signalmanager.h"
#include "fdutils.h"
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <errno.h>

/**
NOTES: 

On normal Linux and OSX systems the timer granularity is limited to a 10ms
interrupt rate. This means that RTSJ timers, periodic thread release and
simple sleeps can only update every 10ms - therefore periods, for example, must
be >> 10ms. (Apparently with 2.6 kernels this will improve to 1ms.)

On Timesys Linux RT, with the licensed extensions we can achieve timer
interrupt rates of 1us by using the POSIX RT timers and linking with the
resource kernel (rk) library.

POSIX RT Timers do not work very well (irregular interrupt rates at less
than the requested rate) or normal Linux systems with a 2.4 kernel. Supposedly
they are fixed in 2.6 but that has not been tested.
Additionally they require linking with libpthread, which can cause problems 
with simplejit and/or j2c on some systems depending on the version of 
LinuxThreads being used. Use with NPTL should be okay but the various working 
permutations have not been enumerated so we keep it simple and don't use the
POSIX timers unless on the licenced Timesys Linux RT with the rk library.

When POSIX RT timers are not used we use setitimer to install a timer "alarm"
handler. Note that neither setitimer nor the POSIX RT timers generate an error
if you ask for an interrupt rate faster than that supported by the system. So
we check if the requested rate is less than the supported rate and print a 
warning if so. It may be better to abort the OVM startup with an error in this
case so that the user is clearly aware that the system can't support the
interrupt rate they requested.

To link with the Timesys rk library you need to ensure that librk.a is either 
in the default link-time path used by ld (/usr/lib only!) or else is specified
on the LIBRARY_PATH environment variable. This must occur at both configure 
time (so that configure will produce makefiles that will use the POSIX RT 
timers) and at build time so that the library can actually be statically linked.
David Holmes - Jul 26, 2004
*/

/* For local experimentation */
/*
#define USE_POSIX_RT_TIMER
*/

#if defined(USE_POSIX_RT_TIMER) && !(_POSIX_TIMERS > 0)
#error "Can't use POSIX timers when they aren't supported on this platform"
#endif


/**
 * The actual interrupt count - read from Java code
 */
volatile sig_atomic_t interruptCount;

/**
 * Returns address of interruptCount for Java code to access directly
 */
volatile void* getInterruptCountAddress() {
    return &interruptCount;
}

/**
 * The currently configured timer period. This should be a constant once
 * set.
 */
jlong currentPeriodNanos;

/** local prototypes */
static void setupTimer(jlong nanos);

/**
 * Implementation of TimerManagerImpl.Helper.getPeriod
 */
jlong getPeriod() {
    return currentPeriodNanos;
}


#define NANOS_PER_SEC (1000L*1000*1000)
#define MICROS_PER_SEC (1000L*1000)
#define NANOS_PER_MICRO 1000L

#if REPORT_TIMER_INTERRUPT_RATE==YES
  #define NSTAMPS 200
  static int idx = 0;

  #if defined(USE_POSIX_RT_TIMER) || (_POSIX_TIMERS > 0)
    /* we have clock_gettime available */
    static struct timespec timestamps[NSTAMPS];

    #define LOG_TIMESTAMP() do { \
      if (idx < NSTAMPS) \
        clock_gettime(CLOCK_REALTIME, &timestamps[idx++]); \
      } while(0)

    static void dumpTimestamps() {
        jlong lastTime, now;
        int i;
        for(i = 1; i < NSTAMPS; i++) {
            lastTime = timestamps[i-1].tv_sec * NANOS_PER_SEC + timestamps[i-1].tv_nsec;
            now = timestamps[i].tv_sec * NANOS_PER_SEC + timestamps[i].tv_nsec;
            
            printf("Time between interrupts = %lld\n", (now - lastTime) );
        }
    }

  #else /* not POSIX TIMER */
    static struct timeval timestamps[NSTAMPS];

    #define LOG_TIMESTAMP() do { \
       if (idx < NSTAMPS) \
         gettimeofday(&timestamps[idx++], NULL); \
       } while(0)

    static void dumpTimestamps() {
        jlong lastTime, now;
        int i;
        for(i = 1; i < NSTAMPS; i++) {
            lastTime = timestamps[i-1].tv_sec * NANOS_PER_SEC + timestamps[i-1].tv_usec * NANOS_PER_MICRO;
            now = timestamps[i].tv_sec * NANOS_PER_SEC + timestamps[i].tv_usec * NANOS_PER_MICRO;
            
            printf("Time between interrupts = %lld\n", (now - lastTime) );
        }
    }
  #endif
#else /* no REPORT_TIMER_INTERRUPT_RATE */
  #define LOG_TIMESTAMP()
#endif



#if defined(USE_POSIX_RT_TIMER)
/* Our real-time timer */
static timer_t timer;

/**
 * Signal handling code for use with POSIX RT timer
 */
static void timer_interrupt_handler(jint sig, siginfo_t *info) {
    SIGNAL_EVENT();
#ifdef DEBUG
    if (sig != SIGALRM){
        printf("Wrong sig sent to handler: expected %d got %d\n", 
               SIGALRM, sig);
        abort();
    }
    if (info->si_signo != SIGALRM){
        printf("Wrong signo sent to handler: expected %d got %d\n", 
               SIGALRM, info->si_signo);
        abort();
    }
    if (info->si_code != SI_TIMER){
        printf("Wrong code sent to handler\n");
        abort();
    }
#endif

    /* account for the possibility that timer interrupts are firing faster
       than the signal system can deliver them.
    */
    interruptCount = interruptCount + 1 + timer_getoverrun(timer);

    LOG_TIMESTAMP();
}

/**
 * Helper function to actually do the timer setup
 */
static void setupTimer(jlong nanos) {
    struct itimerspec ts;
    struct sigevent timer_ev;
    long secs  = nanos / NANOS_PER_SEC;
    nanos = nanos % NANOS_PER_SEC;

    if (1) printf("WORKING WITH POSIX RT TIMERS\n");
    timer_ev.sigev_notify = SIGEV_SIGNAL;
    timer_ev.sigev_signo = SIGALRM;
    EXIT_ERRNO(timer_create(CLOCK_REALTIME, &timer_ev, &timer), "timer_create failed");
    ts.it_interval.tv_sec = secs;
    ts.it_interval.tv_nsec = nanos;
    ts.it_value.tv_sec = secs;
    ts.it_value.tv_nsec = nanos;
    if (1) printf("Timer set for: %ld secs, %lld nsecs\n", secs, nanos);
    EXIT_ERRNO(timer_settime(timer, 0, &ts, NULL), "timer_settime failed");
}

static void tearDownTimer() {
    EXIT_ERRNO(timer_delete(timer), "timer_delete failed");
}

static jlong getTimerRes() {
    struct timespec res;
    clock_getres(CLOCK_REALTIME, &res);
    return ((jlong)res.tv_sec * NANOS_PER_SEC) + res.tv_nsec;
}


#else /* not USE_POSIX_RT_TIMER */


/**
 * Signal handling code for use with setitimer
 */
static void timer_interrupt_handler(jint sig, siginfo_t *info) {
    SIGNAL_EVENT();
#ifdef DEBUG
    if (sig != SIGALRM){
        printf("Wrong sig sent to handler: expected %d got %d\n", 
               SIGALRM, sig);
        abort();
    }
#endif

    interruptCount++;

/*     if (eventsEnabled) putchar('@'); */
/*     else putchar('%'); */

    LOG_TIMESTAMP();
}

static void setupTimer(jlong nanos) {
    struct itimerval iv;
    long secs, usecs;
    if (0) printf("WORKING WITH SETITIMER\n");
    secs  = nanos / NANOS_PER_SEC;
    nanos = nanos % NANOS_PER_SEC;
    usecs = nanos / NANOS_PER_MICRO;
    if (usecs == 0 && secs == 0) {
        /* presumably nanos < 1 usec */
        usecs = 1;
    }
    iv.it_interval.tv_sec = secs;
    iv.it_interval.tv_usec = usecs;
    iv.it_value.tv_sec = secs;
    iv.it_value.tv_usec = usecs;
    if (0) printf("timer set for: %ld secs, %ld usecs\n", secs, usecs);
    EXIT_ERRNO(setitimer(ITIMER_REAL,&iv,NULL), "setitimer failed");
}

static void tearDownTimer() {
    struct itimerval iv;
    iv.it_interval.tv_sec = 0;
    iv.it_interval.tv_usec = 0;
    iv.it_value.tv_sec = 0;
    iv.it_value.tv_usec = 0;
    EXIT_ERRNO(setitimer(ITIMER_REAL,&iv,NULL), "setitimer failed");
}

static jlong getTimerRes() {
    /* normal UNIX/Linux timer interrupt rate - no way to verify ??? :( */
    return 10LL * 1000 * 1000;
}


#endif /* USE_POSIX_RT_TIMER */


static jboolean handlerInstalled = 0;

/**
 * The code for configuring the timer and installing our signal handler
 *
 */
void configureTimer(jlong nanos) {
    jlong timerRes = getTimerRes();

    /* check args as its easy to pass in an overflowed 32-bit value instead of
       a 64-bit value
    */
    if (nanos < 0) {
        fSBE(stderr,1);
        fprintf(stderr,"negative timer period received - make sure it is being passed as a 64-bit value\n");
        exit(-1);
    }

    if (timerRes > nanos)
        fprintf(stderr, "\nWARNING: requested timer resolution not available -"
                "requested %lld ns, available %lld ns\n", nanos, timerRes);

    /* adjust the current period to handle underlying timer resolution*/
    currentPeriodNanos = timerRes * 
        (nanos/timerRes + (nanos%timerRes > 0 ? 1 : 0));

    if (!handlerInstalled) {
/*         printf("REGISTERING TIMER SIGNAL HANDLER\n"); */
        /* set up the signal handler */
        ABORT_ERRNO(registerSignalHandler(SIGALRM,timer_interrupt_handler),
                    "registerSignalHandler");
        handlerInstalled = 1;
    }
    setupTimer(currentPeriodNanos);
}

/* stop the timer and remove the handler */
void stopTimer() {
    tearDownTimer();
    removeSignalHandler(timer_interrupt_handler);
#if REPORT_TIMER_INTERRUPT_RATE==YES
    dumpTimestamps();
#endif
}

















