/*
 * DANG_BEGIN_MODULE
 *
 * Description: Timer handlers for DOSEMU.
 * 
 * Maintainers: Scott Buchholz
 *
 * This code handles periodic routine scheduling for DOSEMU.  For example,
 * if you need to do an event or a class of events at a specified time or
 * set of times, create a timer and this module will take care of calling
 * you back at the appropriate time.
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/timers.h"

#if CONFIG_TIMER_STATS > 0
static Boolean  CONFIG_TIMER_STATS = TRUE;

static u_long   timer_stat_total = 0; /* for one-shots */
static u_long   timer_stat_usecs = 0;
#endif

/*
 * The internal timer structures
 */

struct timer_struct_t {
  struct timer_struct_t  * next;
  struct timeval           pop_time;
  u_long                   usecs;
  u_long                   count;
  emu_timer_funct          func;
  emu_timer_t              pos;
#if CONFIG_TIMER_STATS > 0
  u_long                   stat_total;
  u_long                   stat_usecs;
#endif
};

enum { EMU_MAX_TIMERS=30 };

/*
 * List of valid timers, free timer list, timer array
 */

static struct timer_struct_t * timer_head;
static struct timer_struct_t * timer_free_head;
static struct timer_struct_t * timer_free_tail;
static struct timer_struct_t   timers[EMU_MAX_TIMERS];

/*
 * These values hold the "current time"
 */
static Boolean          timer_is_curtime_valid;
static struct timeval   timer_curtime;

static int              timer_blocked = 0;

/*
 * Request SIGTIMER messages at 100Hz (maximum rate for i386 Linux)
 */
#define TIMER_USECS        10000

/*
 * Initialize the timer module (called at startup)
 *
 * This function also sets up the timer signal handler
 */
void timer_init(void)
{
  struct itimerval itv;
  long timer_usecs;

  /* allow the user to adjust the timer rate for debugging */
  timer_usecs = TIMER_USECS;
  if (getenv("TIMER_RATE"))
    timer_usecs = atol(getenv("TIMER_RATE"));

  timer_reset();

  itv.it_interval.tv_sec  = 0;
  itv.it_interval.tv_usec = timer_usecs;
  itv.it_value.tv_sec     = 0;
  itv.it_value.tv_usec    = timer_usecs;
  g_printf("TIMER: Requesting SIGTIMER signals every %uusecs\n", TIMER_USECS);
  setitimer(ITIMER_PROF, &itv, NULL);
}

/*
 * Reset the timer module (called on reboot)
 */
void timer_reset(void)
{
  emu_timer_t i;

  /* reset all the timers */
  timer_head      = NULL;
  timer_free_head = &timers[0];
  timer_free_tail = &timers[EMU_MAX_TIMERS-1];
  for (i = 0; i < EMU_MAX_TIMERS-1; i++) {
    timers[i].pos  = i+1;
    timers[i].next = &timers[i+1];
  }
  timer_free_tail->pos  = EMU_MAX_TIMERS;
  timer_free_tail->next = NULL;

  timer_is_curtime_valid = FALSE;
  timer_blocked          = 0;

  /* reenable the timer statistics */
  timer_stats_enable();
  timer_stats_reset();
}

/*
 * Compare two timeval's
 */
static inline int timeval_comp(struct timeval a, struct timeval b)
{
  if (a.tv_sec > b.tv_sec)
    return 1;
  if (a.tv_sec < b.tv_sec)
    return -1;
  if (a.tv_usec > b.tv_usec)
    return 1;
  if (a.tv_usec < b.tv_usec)
    return -1;
  return 0;
}

/*
 * Fetch the current time
 */
static inline void timer_get_curtime(struct timeval *curtime)
{
  if (timer_is_curtime_valid)
    *curtime = timer_curtime;
  else
    gettimeofday(curtime, NULL);
}

/*
 * Adjust a "timeval" after we add a period to it
 */
static inline void timer_adjust(struct timeval *adj_time)
{
  adj_time->tv_sec += adj_time->tv_usec / 1000000;
  adj_time->tv_usec = adj_time->tv_usec % 1000000;
}

/*
 * Add the usecs to the timeval
 */
static inline void timer_bump(struct timer_struct_t *ptr)
{
  ptr->pop_time.tv_usec += ptr->usecs;
  timer_adjust(&ptr->pop_time);
}

/*
 * Adjust a "timeval" after we add a period to it
 */
static void timer_reschedule(struct timer_struct_t * ptr)
{
  if (!timer_head || timeval_comp(ptr->pop_time, timer_head->pop_time) < 0) {
    ptr->next  = timer_head;
    timer_head = ptr;
  }
  else {
    struct timer_struct_t * temp = timer_head;
    while (temp->next) {
      if (timeval_comp(ptr->pop_time, temp->next->pop_time) < 0) {
	ptr->next  = temp->next;
	temp->next = ptr;
	break;
      }
      temp = temp->next;
    }
    if (!temp->next) {
      ptr->next  = NULL;
      temp->next = ptr;
    }
  }
}

/*
 * Handle all the events that have been scheduled
 */
void timer_schedule(struct timeval *curtime)
{
  static Boolean timer_in_schedule = FALSE;

  if (!timer_blocked) {
    timer_is_curtime_valid = TRUE;
    timer_curtime          = *curtime;

    if (!timer_in_schedule) {
      struct timer_struct_t * ptr = timer_head;

      timer_in_schedule = TRUE;

      while (ptr && timeval_comp(ptr->pop_time, timer_curtime) < 0) {
	timer_head = ptr->next;
#if CONFIG_TIMER_STATS > 0
	{
	  struct timeval real_time;
	  long usecs;

	  gettimeofday(&real_time, NULL);
	  usecs = (real_time.tv_sec - ptr->pop_time.tv_sec) * 1000000 +
	          real_time.tv_usec - ptr->pop_time.tv_usec;
	  if (ptr->count) {
	    timer_stat_total++;
	    timer_stat_usecs += usecs;
	  }
	  else {
	    ptr->stat_total++;
	    ptr->stat_usecs += usecs;
	  }
	}
#endif
	timer_bump(ptr);
	ptr->func();
	if (ptr->count && !--ptr->count) {
	  timer_free_tail->next = ptr;
	  timer_free_tail       = ptr;
	  timer_free_tail->next = NULL;
	}
	else {
	  timer_reschedule(ptr);
	}

	ptr = timer_head;
      }

      timer_is_curtime_valid = FALSE;
      timer_in_schedule      = FALSE;
    }
  }
}

/*
 * Create a new timer popping at pop_time (or current time + inc if pop_time
 * is NULL), with period usecs_inc; 
 */
emu_timer_t timer_create(emu_timer_funct func, struct timeval *pop_time,
			 u_long usecs_inc)
{
  emu_timer_t timer = 0;
 
  if (timer_free_head != NULL) {
    struct timer_struct_t * ptr = timer_free_head;

    timer_free_head = timer_free_head->next;
    if (timer_free_head == NULL)
      timer_free_tail = NULL;

    ptr->usecs = usecs_inc;
    ptr->count = 0;
    ptr->func  = func;

    if (pop_time) {
      ptr->pop_time = *pop_time;
    }
    else {
      timer_get_curtime(&ptr->pop_time);
      timer_bump(ptr);
    }
#if CONFIG_TIMER_STATS > 0
    ptr->stat_total = 0;
    ptr->stat_usecs = 0;
#endif
    timer_reschedule(ptr);
    timer      = ptr->pos;
  }
  return timer;
}

/*
 * Create a one-shot timer which will pop at current time + usecs
 */
emu_timer_t timer_create_countdown(emu_timer_funct func, u_long usecs)
{
  emu_timer_t timer = 0;
 
  if (timer_free_head != NULL) {
    struct timer_struct_t * ptr = timer_free_head;

    timer_free_head = timer_free_head->next;
    if (timer_free_head == NULL)
      timer_free_tail = NULL;

    ptr->usecs = usecs;
    ptr->count = 1;
    ptr->func  = func;
    timer_get_curtime(&ptr->pop_time);
    timer_bump(ptr);
#if CONFIG_TIMER_STATS > 0
    ptr->stat_total = 0;
    ptr->stat_usecs = 0;
#endif
    timer_reschedule(ptr);
    timer      = ptr->pos;
  }
  return timer;
}

/*
 * Create a one-shot timer which will pop at pop_time
 */
emu_timer_t timer_create_popat(emu_timer_funct func, struct timeval pop_time)
{
  emu_timer_t timer = 0;
 
  if (timer_free_head != NULL) {
    struct timer_struct_t * ptr = timer_free_head;

    timer_free_head = timer_free_head->next;
    if (timer_free_head == NULL)
      timer_free_tail = NULL;

    ptr->usecs = 0;
    ptr->pop_time = pop_time;
    ptr->count = 1;
    ptr->func  = func;
#if CONFIG_TIMER_STATS > 0
    ptr->stat_total = 0;
    ptr->stat_usecs = 0;
#endif
    timer_reschedule(ptr);
    timer      = ptr->pos;
  }
  return timer;
}

/*
 * Change the timer rate and update the pop_time
 */
Boolean timer_modify_usecs(emu_timer_t timer, u_long usecs)
{
  struct timer_struct_t *ptr;

  ptr = timer_head;
  while (ptr && ptr->pos != timer)
    ptr = ptr->next;

  if (ptr) {
    ptr->usecs = usecs;
    timer_get_curtime(&ptr->pop_time);
    timer_bump(ptr);
    return TRUE;
  }
  else
    return FALSE;
}

/*
 * Enable timer statistic collection
 */
void timer_stats_enable(void)
{
#if CONFIG_TIMER_STATS > 0
  CONFIG_TIMER_STATS = TRUE;
#endif
}

/*
 * Disable timer statistic collection
 */
void timer_stats_disable(void)
{
#if CONFIG_TIMER_STATS > 0
  CONFIG_TIMER_STATS = FALSE;
#endif
}

/*
 * Dump the timer statistics
 */
void timer_stats_dump(void)
{
#if CONFIG_TIMER_STATS > 0
  struct timer_struct_t * ptr = timer_head;

  g_printf("TIMERS: timer_stats_dump:\n");
  while (ptr) {
    if (ptr->count == 0) {
      if (ptr->stat_total)
	g_printf("  timer #%d: total = %lu, avg_miss = %lu usecs\n", ptr->pos,
		 ptr->stat_total, ptr->stat_usecs/ptr->stat_total);
      else
	g_printf("  timer #%d: never popped\n", ptr->pos);
    }
    ptr = ptr->next;
  }

  if (timer_stat_total)
    g_printf("  one shots: total = %lu, avg_miss = %lu usecs\n", 
	   timer_stat_total, timer_stat_usecs/timer_stat_total);
  else
    g_printf("  one shots: never popped\n");
  g_printf("\n");
#else
  g_printf("timer stats not compiled\n");
#endif
}

/*
 * Reset the timer statistics
 */
void timer_stats_reset(void)
{
#if CONFIG_TIMER_STATS > 0
  struct timer_struct_t * ptr = timer_head;
  while (ptr) {
    ptr->stat_total = 0;
    ptr->stat_usecs = 0;
    ptr = ptr->next;
  }

  timer_stat_total = 0;
  timer_stat_usecs = 0;
#endif
}

/*
 * Block and unblock the timers (this should rarely be done!)
 */
void timer_block(void)
{
  timer_blocked++;
}

void timer_unblock(void)
{
  timer_blocked--;
}

