#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 (ns) */
	vlong	monolast;	/* last reading of fast clocks for monotonic time */
	vlong	monooff;	/* offset from 0 to monolast (ns) */
	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);
	tod.monolast = tod.last;
	iunlock(&tod);
	todsetfreq(tod.hz);
	addclock0link(todfix, 100);
}

/*
 *  return monotonic ns; tod must be locked
 */
static vlong
todmono(vlong ticks)
{
	uvlong x;
	vlong diff;

	if(tod.hz == 0) /* called from first todsetfreq */
		return 0;
	diff = ticks - tod.monolast;
	mul64fract(&x, diff, tod.multiplier);
	x += tod.monooff;
	return x;
}

/*
 *  calculate multiplier
 */
void
todsetfreq(vlong f)
{
	vlong ticks;

	if (f <= 0)
		panic("todsetfreq: freq %lld <= 0", f);
	ilock(&tod);
	ticks = fastticks(nil);
	tod.monooff = todmono(ticks);
	tod.monolast = ticks;
	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, vlong *monop)
{
	uvlong x;
	vlong ticks, diff, mono;
	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 (except when /dev/[bin]time is written) */
	if(x < tod.lasttime)
		x = tod.lasttime;
	else
		tod.lasttime = x;

	mono = 0;
	if(monop != nil)
		mono = todmono(ticks);
	iunlock(&tod);

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

	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, 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;
}
