/*
 *	cputimerutil.c -- CPU timer utility (calibration etc.)
 *	Copyright (C) 2001, 2005 Fred Barnes <frmb@kent.ac.uk>
 *	Based on code from Marcel Boosten in MESH
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>


typedef struct {
	unsigned long long timeparts[4];
	struct timeval t_start;
	struct timeval t_finish;
} timeinit;

static unsigned long long realtime (void);
static double calc_cpuspeed (timeinit *t);
static double read_clock_from_proc (void);
static double calibrate_clock (void);
static void usage (FILE *stream);

static char *progname = NULL;

#define TIMEINIT_COUNT 1000

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 1
#endif


/*{{{  int main (int argc, char **argv)*/
/*
 *	start here
 */
int main (int argc, char **argv)
{
	double clock_speed;
	int do_proc, do_calib;
	int i;
	char **walk;

	for (progname = *argv + (strlen (*argv) - 1); (*progname != '/') && (progname >= *argv); progname--);
	progname++;
	do_proc = 0;
	do_calib = 0;
	for (i=1, walk=argv+1; (i < argc) && *walk; i++, walk++) {
		if (!strcmp (*walk, "--help") || !strcmp (*walk, "-h")) {
			usage (stdout);
			exit (EXIT_FAILURE);
		}
#if defined(TARGET_CPU_386)
		if (!strcmp (*walk, "-c")) {
			do_calib = 1;
			continue;
		}
#endif
		if (!strcmp (*walk, "-p")) {
			do_proc = 1;
			continue;
		}
	}
	if (!do_proc && !do_calib) {
		do_proc = do_calib = 1;
	}

	clock_speed = 0.0;
	if (do_proc) {
		clock_speed = read_clock_from_proc ();
	}
#if defined(TARGET_CPU_386)
	if ((clock_speed == 0.0) && do_calib) {
		clock_speed = calibrate_clock ();
	}
#endif
	if (clock_speed > 0.0) {
		printf ("%.3f\n", clock_speed);
		return 0;
	}
	return 1;
}
/*}}}*/
#if defined(TARGET_CPU_386)
/*{{{  static unsigned long long realtime (void)*/
/*
 *	reads the CPU timer
 */
static unsigned long long realtime (void)
{
	union {
		struct {
			unsigned int p0;
			unsigned int p1;
		} bits;
		unsigned long long time;
	} u;
	__asm__ __volatile__ (".byte 0x0f,0x31" : "=a" (u.bits.p0), "=d" (u.bits.p1));

	return u.time;
}
/*}}}*/
/*{{{  static double calc_cpuspeed (timeinit *t)*/
/*
 *	calculates CPU speed
 */
static double calc_cpuspeed (timeinit *t)
{
	double cpu_speed;
	double day_diff;

	day_diff = (1e6 * (t->t_finish.tv_sec - t->t_start.tv_sec)) + (double)(t->t_finish.tv_usec - t->t_start.tv_usec);
	cpu_speed = (double)(t->timeparts[3] - t->timeparts[1]) / day_diff;
	if (((t->timeparts[3] - t->timeparts[2]) < 10000) && ((t->timeparts[1] - t->timeparts[0]) < 10000)) {
		return cpu_speed;
	}
	return 0.0;
}
/*}}}*/
/*{{{  static double calibrate_clock (void)*/
/*
 *	calibrates the clock using spinning time-delays
 */
static double calibrate_clock (void)
{
	int i, n_valid;
	double sum, cpu_speed;
	timeinit tdata[TIMEINIT_COUNT];

	sum = 0.0;
	n_valid = 0;
	for (i=0; i<TIMEINIT_COUNT; i++) {
		tdata[i].timeparts[0] = realtime ();
		gettimeofday (&tdata[i].t_start, NULL);
		tdata[i].timeparts[1] = realtime ();
	}
	sleep (2);
	for (i=0; i<TIMEINIT_COUNT; i++) {
		tdata[i].timeparts[2] = realtime ();
		gettimeofday (&tdata[i].t_finish, NULL);
		tdata[i].timeparts[3] = realtime ();
		cpu_speed = calc_cpuspeed (&tdata[i]);
		if (cpu_speed > 0.0) {
			sum += cpu_speed;
			n_valid++;
		}
	}

	if (n_valid) {
		cpu_speed = sum / n_valid;
	} else {
		cpu_speed = 0.0;
	}
	return cpu_speed;
}
/*}}}*/
#endif	/* defined(TARGET_CPU_386) */
/*{{{  static double read_clock_from_proc (void)*/
/*
 *	attempts to read the clock from /proc/cpuinfo
 *	returns 0.0 on failure
 */
static double read_clock_from_proc (void)
{
	FILE *fp;
	char buffer[128], *ch;
	double klock;

	fp = fopen ("/proc/cpuinfo", "r");
	if (!fp) {
		return 0.0;
	}
	while (fgets (buffer, 128, fp)) {
		if (!strncmp (buffer, "cpu MHz", 7)) {
			/* probably */
			for (ch=buffer; (*ch != '\0') && (*ch != ':'); ch++);
			if (*ch == '\0') {
				fclose (fp);
				return 0.0;
			}
			for (ch++; (*ch != '\0') && ((*ch < '0') || (*ch > '9')); ch++);
			if (*ch == '\0') {
				fclose (fp);
				return 0.0;
			}
			if (sscanf (ch, "%lf", &klock) != 1) {
				fclose (fp);
				return 0.0;
			}
			break;
		}
	}
	fclose (fp);
	return klock;
}
/*}}}*/
/*{{{  static void usage (FILE *stream)*/
/*
 *	dumps usage information
 */
static void usage (FILE *stream)
{
	fprintf (stream, "usage: %s [options]\n", progname);
	fprintf (stream, "where options are:\n");
	fprintf (stream, "  -h | --help    show this help\n");
	fprintf (stream, "  -p             read CPU speed from /proc/cpuinfo\n");
#if defined(TARGET_CPU_386)
	fprintf (stream, "  -c             calibrate clock speed\n");
#endif
	fprintf (stream, "(by default -p will be tried first, then -c if that fails)\n");
	return;
}
/*}}}*/

