#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "../port/error.h"

/*
* Compute nanosecond epoch time from the fastest ticking clock
* on the system.  Converting the time to nanoseconds requires
* the following formula
*
*      t = (((1000000000<<31)/f)*ticks)>>31
*
*  where
*
*      'f'             is the clock frequency
*      'ticks'         are clock ticks
*
*  to avoid too much calculation in todget(), we calculate
*
*      mult = (1000000000<<32)/f
*
*  each time f is set.  f is normally set by a user level
*  program writing to /dev/fastclock.  mul64fract will then
*  take that fractional multiplier and a 64 bit integer and
*  return the resulting integer product.
*
*  We assume that the cpu's of a multiprocessor are synchronized.
*  This assumption needs to be questioned with each new architecture.
*/

/* frequency of the tod clock */
#define TODFREQ         1000000000ULL
#define MicroFREQ       1000000ULL

struct {
       int     init;           /* true if initialized */
       ulong   cnt;
       Lock;
       uvlong  multiplier;     /* ns = off + (multiplier*ticks)>>31 */
       uvlong  divider;        /* ticks = (divider*(ns-off))>>31 */
       uvlong  umultiplier;    /* µs = (µmultiplier*ticks)>>31 */
       uvlong  udivider;       /* ticks = (µdivider*µs)>>31 */
       vlong   hz;             /* frequency of fast clock */
       vlong   last;           /* last reading of fast clock */
       vlong   off;            /* offset from epoch to last */
       vlong   lasttime;       /* last return value from todget */
       vlong   delta;  /* add 'delta' each slow clock tick from sstart to send */
       ulong   sstart;         /* ... */
       ulong   send;           /* ... */
} tod;

static void todfix(void);

void
todinit(void)
{
       if(tod.init)
               return;
       ilock(&tod);
       tod.init = 1;                   /* prevent reentry via fastticks */
       tod.last = fastticks((uvlong *)&tod.hz);
       iunlock(&tod);
       todsetfreq(tod.hz);
       addclock0link(todfix, 100);
}

/*
*  calculate multiplier
*/
void
todsetfreq(vlong f)
{
       if (f <= 0)
               panic("todsetfreq: freq %lld <= 0", f);
       ilock(&tod);
       tod.hz = f;

       /* calculate multiplier for time conversion */
       tod.multiplier = mk64fract(TODFREQ, f);
       tod.divider = mk64fract(f, TODFREQ) + 1;
       tod.umultiplier = mk64fract(MicroFREQ, f);
       tod.udivider = mk64fract(f, MicroFREQ) + 1;
       iunlock(&tod);
}

/*
*  Set the time of day struct
*/
void
todset(vlong t, vlong delta, int n)
{
       if(!tod.init)
               todinit();

       ilock(&tod);
       if(t >= 0){
               tod.off = t;
               tod.last = fastticks(nil);
               tod.lasttime = 0;
               tod.delta = 0;
               tod.sstart = tod.send;
       } else {
               if(n <= 0)
                       n = 1;
               n *= HZ;
               if(delta < 0 && n > -delta)
                       n = -delta;
               if(delta > 0 && n > delta)
                       n = delta;
               if (n == 0) {
                       iprint("todset: n == 0, delta == %lld\n", delta);
                       delta = 0;
               } else
                       delta /= n;
               tod.sstart = MACHP(0)->ticks;
               tod.send = tod.sstart + n;
               tod.delta = delta;
       }
       iunlock(&tod);
}

/*
*  get time of day
*/
vlong
todget(vlong *ticksp)
{
       uvlong x;
       vlong ticks, diff;
       ulong t;

       if(!tod.init)
               todinit();

       /*
        * we don't want time to pass twixt the measuring of fastticks
        * and grabbing tod.last.  Also none of the vlongs are atomic so
        * we have to look at them inside the lock.
        */
       ilock(&tod);
       tod.cnt++;
       ticks = fastticks(nil);

       /* add in correction */
       if(tod.sstart != tod.send){
               t = MACHP(0)->ticks;
               if(t >= tod.send)
                       t = tod.send;
               tod.off = tod.off + tod.delta*(t - tod.sstart);
               tod.sstart = t;
       }

       /* convert to epoch */
       diff = ticks - tod.last;
       if(diff < 0)
               diff = 0;
       mul64fract(&x, diff, tod.multiplier);
       x += tod.off;

       /* time can't go backwards */
       if(x < tod.lasttime)
               x = tod.lasttime;
       else
               tod.lasttime = x;

       iunlock(&tod);

       if(ticksp != nil)
               *ticksp = ticks;

       return x;
}

/*
*  convert time of day to ticks
*/
uvlong
tod2fastticks(vlong ns)
{
       uvlong x;

       ilock(&tod);
       mul64fract(&x, ns-tod.off, tod.divider);
       x += tod.last;
       iunlock(&tod);
       return x;
}

/*
*  called regularly to avoid calculation overflows
*/
static void
todfix(void)
{
       vlong ticks, diff;
       uvlong x;

       ticks = fastticks(nil);
       diff = ticks - tod.last;
       if(diff <= tod.hz)
               return;

       ilock(&tod);
       diff = ticks - tod.last;
       if(diff > tod.hz){
               /* convert to epoch */
               mul64fract(&x, diff, tod.multiplier);
               x += tod.off;

               /* protect against overflows */
               tod.last = ticks;
               tod.off = x;
       }
       iunlock(&tod);
}

long
seconds(void)
{
       return (vlong)todget(nil) / TODFREQ;
}

uvlong
fastticks2us(uvlong ticks)
{
       uvlong res;

       if(!tod.init)
               todinit();
       mul64fract(&res, ticks, tod.umultiplier);
       return res;
}

uvlong
us2fastticks(uvlong us)
{
       uvlong res;

       if(!tod.init)
               todinit();
       mul64fract(&res, us, tod.udivider);
       return res;
}

/*
*  convert milliseconds to fast ticks
*/
uvlong
ms2fastticks(ulong ms)
{
       if(!tod.init)
               todinit();
       return (tod.hz*ms)/1000ULL;
}

/*
*  convert nanoseconds to fast ticks
*/
uvlong
ns2fastticks(uvlong ns)
{
       uvlong res;

       if(!tod.init)
               todinit();
       mul64fract(&res, ns, tod.divider);
       return res;
}

/*
*  convert fast ticks to ns
*/
uvlong
fastticks2ns(uvlong ticks)
{
       uvlong res;

       if(!tod.init)
               todinit();
       mul64fract(&res, ticks, tod.multiplier);
       return res;
}

/*
* Make a 64 bit fixed point number that has a decimal point
* to the left of the low order 32 bits.  This is used with
* mul64fract for converting twixt nanoseconds and fastticks.
*
*      multiplier = (to<<32)/from
*/
uvlong
mk64fract(uvlong to, uvlong from)
{
/*
       int shift;

       if(to == 0ULL)
               return 0ULL;

       shift = 0;
       while(shift < 32 && to < (1ULL<<(32+24))){
               to <<= 8;
               shift += 8;
       }
       while(shift < 32 && to < (1ULL<<(32+31))){
               to <<= 1;
               shift += 1;
       }

       return (to/from)<<(32-shift);
*/
       return (to<<32) / from;
}