/*
  more stuff that gets called from client/common code.
  This is our hardware clock reader.
  
  Written by Cyrus Patel <cyp@fb14.uni-mainz.de>
*/

#ifdef __cplusplus
extern "C"
{
#endif
#include <time.h>     /* time(NULL) */
#include <sys/time.h> /* struct timeval */
extern unsigned long GetCurrentTime(); /* kernel's GetCurrentTicks() */
extern unsigned long GetCurrentTicks();
extern unsigned long GetSuperHighResolutionTimer(void); /* "838ns" LIE! */
extern unsigned long GetHighResolutionTimer(void); /* 100us counter */
extern unsigned int GetFileServerMajorVersionNumber(void);
extern void ConsolePrintf( /* const */ char *,...); /* for debugging */
#ifdef __cplusplus
}
#endif

/* ===================================================================== */

#ifdef SHOW_TIMERRES
static void checktimerres(void)
{
  unsigned long ubest = 0, uworst = 0, counter = 0;
  unsigned long utotal = 0;
  unsigned long ta, tb;
  struct timeval tv1, tv2;
  unsigned long usecs;
  ta = tb = 0;

  for (counter=0;ta == tb || counter<50;counter++)
  {
    nwCliGetHardwareClock(&tv1);

    ta = GetCurrentTicks();
    if (tb == 0)
      tb = ta;

    do 
    { 
      nwCliGetHardwareClock(&tv2);
      ConsolePrintf("TimerRes: %lu %lu:%lu usecs\r\n\n", time(NULL), tv2.tv_sec, tv2.tv_usec );
    } while ((tv2.tv_sec == tv1.tv_sec) && (tv2.tv_usec == tv1.tv_usec));

    if (tv2.tv_usec < tv1.tv_usec)
    {
      tv2.tv_sec--;
      tv2.tv_usec += 1000000UL;
    }
    usecs = ((tv2.tv_sec - tv1.tv_sec)*1000000ul)+                 (tv2.tv_usec - tv1.tv_usec);
    if (counter == 0 || usecs < ubest) 
      ubest = usecs;
    if (counter == 0 || usecs > uworst)
      uworst = usecs;
    utotal += usecs;  
  }  
  ConsolePrintf("TimerRes: best/avg/worst %lu/%lu/%lu usecs\r\n\n",
                    ubest, utotal/counter, uworst );

  ThreadSwitchWithDelay();
  ta = tb = GetCurrentTicks();
  while (ta == tb)
    ta = GetCurrentTicks();
  nwCliGetHardwareClock(&tv1);
  tb = ta; 
  while (ta == tb)
    ta = GetCurrentTicks();
  nwCliGetHardwareClock(&tv2);
  ThreadSwitchWithDelay();

  ta = tv2.tv_usec;
  tb = tv2.tv_sec;           
  if (tv2.tv_usec < tv1.tv_usec)
  {
    tb--;
    ta += 1000000UL;
  }
  usecs = ((tb - tv1.tv_sec)*1000000ul)+(tb - tv1.tv_usec);

  ConsolePrintf("Cycles/Tick: %lu ( %lu:%lu -> %lu:%lu)\r\n",
                usecs, tv1.tv_sec, tv1.tv_usec, tv2.tv_sec, tv2.tv_usec );

  if (GetCurrentTime() != GetCurrentTicks())
  {
    ThreadSwitchWithDelay();
    ta = tb = GetCurrentTime();
    while (ta == tb)
      ta = GetCurrentTime();
    nwCliGetHardwareClock(&tv1);
    tb = ta; 
    while (ta == tb)
      ta = GetCurrentTime();
    nwCliGetHardwareClock(&tv2);
    ThreadSwitchWithDelay();

    ta = tv2.tv_usec;
    tb = tv2.tv_sec;           
    if (tv2.tv_usec < tv1.tv_usec)
    {
      tb--;
      ta += 1000000UL;
    }
    usecs = ((tb - tv1.tv_sec)*1000000ul)+(tb - tv1.tv_usec);

  }
  ConsolePrintf("Cycles/Time: %lu ( %lu:%lu -> %lu:%lu)\r\n",
                 usecs, tv1.tv_sec, tv1.tv_usec, tv2.tv_sec, tv2.tv_usec );
                 
  return;
}
#endif

/* ===================================================================== */

int __normal_gethardwaretimeofday(struct timeval *tv) /* VERSION A */
{
  static int needreset = -1; /* uninitialized */
  static unsigned long last_husecs, base_time; 
  unsigned long secs = 0, husecs = GetHighResolutionTimer();

  if (needreset < 0) /* uninitialized */
  {
    base_time = last_husecs = 0;
    needreset = +1;  /* fall into next section */
    #ifdef SHOW_TIMERRES
    checktimerres(); 
    #endif
  }
  else if (husecs < last_husecs)  /* wrapped (4.97 days) */
    needreset = +1;
#if 0 /* this is great! but only depending on when we've started. :( */
  else if ((((secs = time(NULL)) * 10000UL) + (husecs % 10000UL))>last_husecs)
    needreset = +1;
#endif
  if (needreset)
  {
    base_time = 0;
    if (secs == 0)
      secs = ((unsigned long)time(NULL));
    husecs = GetHighResolutionTimer(); /* 100 microsecs per count */
    if (secs != ((unsigned long)time(NULL)))
    {
      secs++;
      husecs = GetHighResolutionTimer();
    }
    base_time = secs - (husecs/10000);
    needreset = 0;
/* ConsolePrintf("%ld: sync'd\r\n", time(NULL) ); */
  }
  last_husecs = husecs;

  tv->tv_sec = (time_t)(base_time + (husecs/10000));
  tv->tv_usec = ((husecs % 10000)*100);
  return 0;
}  

/* ===================================================================== */

static unsigned long fast_read_timer0(void)
{
  unsigned int pit;
  _asm xor  eax, eax
  _asm mov  dx, 43h 
  _asm out  dx, al  /* outb(TIMER_MODE, TIMER_SEL0 | TIMER_LATCH); */
  _asm mov  dx, 40h
  _asm in   al, dx  /* lsb = inb(TIMER_CNTR0); */
  _asm xchg ah, al
  _asm in   al, dx  /* msb = inb(TIMER_CNTR0); */
  _asm xchg ah, al
  _asm not  ax      /* counts down from 0xffff, so invert it */
  _asm mov  pit, eax
  return pit;
}  

static unsigned long __myGetSuperHighResolutionTimer(void)
{
  static int need_init = 1;
  unsigned long lo32, hi32 = GetCurrentTime();
  if (need_init)
  {
    /* make sure that the timer is mode 2 (rate generator) */
    /* rather than mode 3 (square wave), which doesn't count linearly. */
    /* it _should_ already be mode 2, but make sure anyway */
    lo32 = hi32;
    while (hi32 == lo32) /* wait for rollover */
      hi32 = GetCurrentTime(); 
    _asm mov  dx, 43h /* RW PIT mode port, control word reg for counters 0-2 */
    _asm mov  al, 34h /* 00 11 010 0 (ctr 0; r/w lo+hi; mode 2; 16bit binary)*/
    _asm out  dx, al  /* outportb(0x43, 0x34); */
    _asm mov  dx, 40h /* RW PIT counter 0, counter divisor */
    _asm xor  al, al  /* default is 0x0000 (65536) (18.2 per sec) */
    _asm out  dx, al  /* outportb(0x40, 0); */
    _asm out  dx, al  /* outportb(0x40, 0); */
    need_init = 0;
  }
  for (;;)
  {
    unsigned long l = hi32;
    lo32 = fast_read_timer0();
    hi32 = GetCurrentTime();
    if (l == hi32)
      break;
  }
  return ((hi32 << 16)|lo32);
}

static int rtcin(int reg)
{
  unsigned char val = (unsigned char)(reg & 0xff);
  _asm mov dx, 70h  /* IO_RTC */
  _asm mov al, val
  _asm out dx, al
  _asm in  al, 84h  /* bus settle delay */
  _asm inc dx       /* IO_RTC + 1 */
  _asm in  al, dx
  _asm mov val, al
  return val;
}  

/*
 * MC146818 RTC Register locations
*/

#define RTC_SEC     0x00  /* seconds */

#define RTC_STATUSD 0x0d  /* status register D (R) Lost Power */
#define RTCSD_PWR   0x80  /* clock power OK */
#define RTC_STATUSA 0x0a  /* status register A */
#define RTCSA_TUP   0x80  /* time update, don't look now */

static unsigned int calibrate_clock( unsigned long (*gettimer0)(void),
                                     const char *clockname )
{
  #define timer0_max_count 0xffff
  #define TIMER_FREQUENCY 1193182 /* 1193181.666... */

  if (!clockname)
    clockname = "i8254 clock";

  //if (GetFileServerMajorVersionNumber() < 4)
  {
    long calvalue = -1;
  
    //if (gettimer0 == __myGetSuperHighResolutionTimer)
    //  gettimer0 = fast_read_timer0;
  
    ConsolePrintf("Calibrating %s...\r\n", clockname );
    CYieldWithDelay();
  
    if ((rtcin(RTC_STATUSD) & RTCSD_PWR) != 0)
    {
      int timeout = 100000000;
      while (calvalue == -1) /* Read the mc146818A seconds counter. */
      {
        if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) 
        {
          int sec = rtcin(RTC_SEC);
          int start_sec = sec;
          CYieldIfNeeded();
          while (calvalue == -1) /* Wait for the mC146818A secs ctr to change. */
          {
            if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) 
            {
              sec = rtcin(RTC_SEC);
              CYieldIfNeeded();
              if (sec != start_sec) /* Start tracking the i8254 counter. */
              {
                unsigned int prev_count = ((~((*gettimer0)())) & 0xffff);
                if (prev_count == 0 || prev_count > timer0_max_count)
                  calvalue = 0;
                else
                {
                  unsigned int tot_count = 0;
                  start_sec = sec;
                  while (calvalue == -1) /* Wait for mc146818A secs ctr change */
                  {
                    /*
                     * Read the i8254 counter for each iteration since this is 
                     * convenient and only costs a few usec of inaccuracy. The 
                     * timing of the final reads of the counters almost matches 
                     * the timing of the initial reads, so the main cause of 
                     * inaccuracy is the varying latency from inside (*gettimer0)
                     * or rtcin(RTC_STATUSA) to the beginning of the rtcin(_SEC) 
                     * that returns a changed seconds count.  The maximum 
                     * inaccuracy from this cause is < 10 usec on 486's.
                    */
                    unsigned int count;
                    if (!(rtcin(RTC_STATUSA) & RTCSA_TUP))
                      sec = rtcin(RTC_SEC);
                    count = ((~((*gettimer0)())) & 0xffff);
                    if (count == 0 || count > timer0_max_count)
                      calvalue = 0;
                    else
                    {
                      if (count > prev_count)
                        tot_count += prev_count - (count - timer0_max_count);
                      else
                        tot_count += prev_count - count;
                      prev_count = count;
                      if (sec != start_sec)
                        calvalue = tot_count; /* FINI */
                      else if (--timeout == 0)
                        calvalue = 0;
                    }
                  }
                }
              }
            }
            else if (--timeout == 0)
              calvalue = 0;
          }
        }
        else if (--timeout == 0)
          calvalue = 0;
      }
    }
  
    CYieldWithDelay();
  
    if (calvalue > 0)
    {
      ConsolePrintf("Calibration completed: %s: %u Hz\n", clockname,
                    (unsigned int )(calvalue));
      //return ((unsigned int)calvalue);
    }
    else
    {
      ConsolePrintf("Calibration failed, default i8254 clock: %u Hz\n",
                    TIMER_FREQUENCY);
    }                  
  }
  return (TIMER_FREQUENCY);
  
  #undef timer0_max_count
  #undef TIMER_FREQUENCY
}


int __super_gethardwaretimeofday(struct timeval *tv) /* VERSION B */
{
  static unsigned long (*gettimer0)(void) = ((unsigned long (*)(void))0x01);
  static unsigned int timer0_frequency = 0;
  static unsigned long base_time = 0, lasttics = 0;
  static unsigned short tickoflowcounter = 0;  /* hi  16 bits */
  unsigned long hitics;                        /* mid 32 bits */
  unsigned short lotics = 0;                   /* low 16 bits */
  unsigned long hi32, lo32;

  if (gettimer0 == ((unsigned long (*)(void))0x01)) /* uninitialized */
  {
    unsigned long (*tmp_gettimer)(void) = GetSuperHighResolutionTimer;
    unsigned long tmp_timer0_freq;

    hi32 = GetCurrentTime();
    lo32 = GetSuperHighResolutionTimer();
             /* => hi16 == low16bits of GetCurrentTime() */
             /* => lo16 == 8254 channel 0 counter */
    hi32 = hi32 & 0xffff;
    lo32 = (lo32 >> 16) & 0xffff;
    if (lo32 < hi32 || lo32 > (hi32+1))
    {
      ConsolePrintf("GetSuperHighResolutionTimer() does not work!\r\n"
                    "  The SDK says 838nanosecs, but that doesn't jive.\r\n"
                    "  (returns 0x%04xxxx but should be ~0x%04xxxx)\r\n",
                       (unsigned int)lo32, (unsigned int)hi32 );
      tmp_gettimer = __myGetSuperHighResolutionTimer;
      __myGetSuperHighResolutionTimer(); /* initialize it */
    }
    tmp_timer0_freq = calibrate_clock(tmp_gettimer, 0);

    base_time = lasttics = 0;
    tickoflowcounter = 0;
    timer0_frequency = tmp_timer0_freq;
    gettimer0 = tmp_gettimer;
    #ifdef SHOW_TIMERRES
    checktimerres();
    #endif
  }  

  do
  {
    hitics = GetCurrentTime();
    lo32 = (*gettimer0)();
    lotics = (unsigned short)(lo32 & 0xffff);
  } while ((lo32 >> 16) != (hitics & 0xffff));
    
  _asm
  { 
    mov  eax, hitics 
    mov  edx, eax 
    mov  dx, tickoflowcounter 
    rol  edx, 16 
    shl  eax, 16 
    mov  ax, lotics 
    mov  ecx, timer0_frequency
    div  ecx 
    mov  hi32, eax /* secs */ 
    mov  eax, edx /*remainder */ 
    xor  edx, edx
    mov  ecx, 1000000
    mul  ecx
    mov  ecx, timer0_frequency
    div  ecx
    mov  lo32, eax /* usecs */
  } 

  if (hitics < lasttics)
  {
    if (hitics < 0x80000000ul)
      tickoflowcounter++;
    base_time = 0;
  }
  if (base_time == 0)
    base_time = ((unsigned long)time(NULL)) - hi32;
  lasttics = hitics;

  hi32 += base_time;
  tv->tv_sec = hi32;
  tv->tv_usec = lo32;
  return 0;
}

/* ===================================================================== */

int nwCliGetTimeOfDay(struct timeval *tv)
{
  static unsigned int last_hsecs = 999;
  static time_t last_secs;
  unsigned long hsecs;
  time_t secs;
  
  /* this section is equivalent to clock(); */
  {
    static unsigned long base_ticks = 0xfffffffful;
    unsigned long ticks = GetCurrentTime();
    if (base_ticks == 0xfffffffful || base_ticks > ticks)
      base_ticks = ticks; /* just some number in the past */
    hsecs = (((ticks - base_ticks) * 11UL) >> 1);
    hsecs -= (hsecs / 738UL);
  }
  
  secs = time(NULL);
  hsecs %= 100; /* we only want the fraction */
  if (last_hsecs != 999)
  {
    if ((secs < last_secs) || (secs == last_secs && hsecs < last_hsecs))
    {
      secs = last_secs;
      hsecs = last_hsecs;
    } 
  }
  last_secs = secs;
  last_hsecs = hsecs;
  
  tv->tv_sec = secs;
  tv->tv_usec = 10000ul * hsecs;
  return 0;
} 

/* ===================================================================== */

struct __my64 { unsigned long lo; unsigned long hi; };

int nwCliGetHardwareClock(struct timeval *tv)
{
#if 0
  static int ver = -1;
  if (ver == -1)
  {
    ver = (GetFileServerMajorVersionNumber()*100)+
           GetFileServerMinorVersionNumber();
    ThreadSwitchWithDelay();
    calibrate_clock( GetSuperHighResolutionTimer, "GetSuperHighResolutionTimer()" );
    ThreadSwitchWithDelay();
    calibrate_clock( GetHighResolutionTimer, "GetHighResolutionTimer()" );
    ThreadSwitchWithDelay();
  }
#endif
  #if 0
  static unsigned int ver = 0xffff;
  if (ver == 0xffff)
    ver = GetFileServerMajorVersionNumber();
  if (ver < 4)
    return __super_gethardwaretimeofday(tv); /* from GetSuper... */
  #endif
  return __normal_gethardwaretimeofday(tv); /* from GetHighResolutionTimer */
}  

