/*
 * Copyright 2006, Steven Rostedt
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <sched.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include "hack.h"

#define rdtsc(low,high) \
     __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

#define rdtscl(low) \
     __asm__ __volatile__("rdtsc" : "=a" (low) : : "edx")

#define rdtscll(val) \
     __asm__ __volatile__("rdtsc" : "=A" (val))

#define LOOPS 10.0

int siginfo;
int posix;

timer_t timerid;

void sig(int sig)
{
	siginfo = sig;
	return;
}

static int set_timer(unsigned long secs, unsigned long nsecs)
{
	int ret;

	if (posix) {
		struct itimerspec val;

		memset(&val, 0, sizeof(val));
		val.it_value.tv_sec = secs;
		val.it_value.tv_nsec = nsecs;
		ret = timer_settime(timerid, 0, &val, NULL);
	} else {
		struct itimerval val;

		if (nsecs < 1000)
			nsecs = 1000;
		memset(&val, 0, sizeof(val));
		val.it_value.tv_sec = secs;
		val.it_value.tv_usec = nsecs / 1000;
		ret = setitimer(ITIMER_REAL, &val, NULL);
	}
	return ret;
}

static void wait_for_second(struct timeval *tv)
{
	struct timeval stv;
	
	gettimeofday(&stv, NULL);
	do {
		gettimeofday(tv, NULL);
	} while(tv->tv_sec == stv.tv_sec);
}

void print_time(double time)
{
	if (time < 0.000001)
		printf("(%.3f nsecs)",time * 1000000000);
	else if (time < 0.001)
		printf("(%.3f usecs)",time * 1000000);
	else if (time < 1)
		printf("(%.3f msecs)",time * 1000);
	else
		printf("(%.3f secs)",time);
}

static pid_t busy_loop(void)
{
	pid_t pid;
	struct sched_param sp;

	if ((pid = fork()) < 0) {
		perror("fork");
	} else if (pid) {
		/* parent */
		return pid;
	}

	if (sched_getparam(0, &sp) < 0) {
		perror("sched_getparam");
		exit(-1);
	}
	if (sp.sched_priority > 0) {
		sp.sched_priority -= 1;
		/*
		 * Make sure that we change our priority, otherwise
		 * we may deadlock the system!
		 */
		if (sched_setparam(0, &sp) < 0) {
			perror("sched_setparam");
			exit(-1);
		}
	}

	for (;;)
		/* Weeee!!!! */
		;
}

void usage(char **argv)
{
	char *p;
	for (p = argv[0]+strlen(argv[0]); p >= argv[0] && *p != '/'; p--)
		;
	printf("\nusage: %s [-bPh] [-c calibrate]\n",p);
	printf("   -P  to use posix timer_settime instead of setitimer\n"
	       "   -b  create a high priority busy loop\n"
	       "   -c  don't calibrate, but use the number given\n"
	       "\n");
	exit(-1);
}

int main(int argc, char **argv)
{
	int c;
	unsigned long long start, end, tps = 0;
	double cal;
	double max=0,min=0,avg=0;
	double err;
	double times[] = { 
			   0.000001, 0.00001, 0.0001,
			   0.001, 0.01, 0.1, 1.0,
			   1.000001, 1.00001, 1.0001,
			   1.001, 1.01, 1.1, 2.0 };
	int i;
	int x;
	pid_t pid = 0;
	struct sched_param sp;
	double sleeptime;
	unsigned long sec,nsec;
	struct timeval stv, tv;
	struct timespec ts;
	int busy = 0;
	int pipe = 0;
	int hack = 0;

	while ((c=getopt(argc, argv, "H:pbhPc:")) >= 0) {
		switch (c) {
			case 'p':
				posix = 1;
				break;
			case 'b':
				busy = 1;
				break;
			case 'c':
				tps = strtoll(optarg,NULL,10);
				break;
			case 'H':
				hack = atoi(optarg);
				break;
			case 'P':
				pipe = 1;
				break;
			case 'h':
			default:
				usage(argv);
		}
	}

	/*
	 * Start hack before increasing priority.
	 */
	if (hack)
		start_hack(pipe, hack);

	clock_getres(CLOCK_MONOTONIC, &ts);
	printf("reported res = %ld.%09ld\n",
			ts.tv_sec,
			ts.tv_nsec);

	/*
	 * Make ourselves highest priority
	 */
	memset(&sp,0,sizeof(sp));
	sp.sched_priority = sched_get_priority_max(SCHED_FIFO);
	sched_setscheduler(0, SCHED_FIFO, &sp);

	if (!tps) {
		printf("starting calibrate\n");
		wait_for_second(&stv);
		rdtscll(start);
		do {
			gettimeofday(&tv, NULL);
		} while(tv.tv_sec < stv.tv_sec+1);
		rdtscll(end);

		tps = end - start;
		// tps >>= 2;
		printf("finished calibrate: ");
	} else {
		printf("passed in calibrate: ");
	}
	cal = tps;
	cal /= 1000000.0;

	if (timer_create(CLOCK_REALTIME, NULL, &timerid) < 0)
		perror("timer_create");
	 
	printf("%.4fMHz %llu\n\n",cal,tps);

	printf("  Requested timex       Max       "
		"     Min            error\n");
	printf("  ---------------       ---       "
		"     ---            -----\n");

	signal(SIGALRM, sig);

	/*
	 * kick off high priority busy loop (lower prio than us)
	 */
	if (busy)
		pid = busy_loop();

	for (x=0; x < sizeof(times)/sizeof(double); x++) {
		sleeptime = times[x];
		sec = sleeptime;
		nsec = (sleeptime-(double)sec) * 1000000000.0;
		min = 999999999.0;
		max = 0.0;
	
		for (i=-1; i < LOOPS; i++) {
			unsigned long long t;
			siginfo = 0;
			if (set_timer(sec, nsec))
				perror("set_timer");
			rdtscll(start);
			sleep(5);
			rdtscll(end);
			if (siginfo != SIGALRM)
				printf("hmm, siginfo = %d\n", siginfo);
			t = end - start;
			if (i < 0)
				continue;
			cal = t;
			cal /= (double)tps;
			if (!i || cal > max)
				max = cal;
			if (!i || cal < min)
				min = cal;
			avg += cal;
		}
		
		avg /= LOOPS;
		
		err = max - sleeptime;
		
		printf("  %.9f        %.9f  %s%.9f   %.9f  ",
		       sleeptime, max,
		       sleeptime > min ? "*" : " ",
		       min, err);
		print_time(err);
		printf("\n");
	}

	if (hack)
		stop_hack();

	if (pid > 0)
		kill(pid, SIGKILL);

	exit(0);
}
