/* Copyright (C) 2006 Andi Kleen, SuSE Labs.
   Decode TSC value into human readable uptime

   mcelog 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; version
   2.

   dmi 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 find a copy of v2 of the GNU General Public License somewhere
   on your Linux system; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#define _GNU_SOURCE 1
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include "memutil.h"
#include "mcelog.h"
#include "tsc.h"
#include "intel.h"

static unsigned scale(u64 *tsc, unsigned unit, double mhz)
{
	u64 v = (u64)(mhz * 1000000) * unit;
	unsigned u = *tsc / v;
	*tsc -= u * v;
	return u;
}

static int fmt_tsc(char **buf, u64 tsc, double mhz)
{
	unsigned days, hours, mins, secs;
	if (mhz == 0.0)
		return -1;
	days = scale(&tsc, 3600 * 24, mhz);
	hours = scale(&tsc, 3600, mhz);
	mins = scale(&tsc, 60, mhz);
	secs = scale(&tsc, 1, mhz);
	xasprintf(buf, "[at %.0f Mhz %u days %u:%u:%u uptime (unreliable)]",
		mhz, days, hours, mins, secs);
	return 0;
}

static double cpufreq_mhz(int cpu, double infomhz)
{
	double mhz;
	FILE *f;
	char *fn;
	xasprintf(&fn, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", cpu);
	f = fopen(fn, "r");
	free(fn);
	if (!f) {
		/* /sys exists, but no cpufreq -- use value from cpuinfo */
		if (access("/sys/devices", F_OK) == 0)
			return infomhz;
		/* /sys not mounted. We don't know if cpufreq is active
		   or not, so must fallback */
		return 0.0;
	}
	if (fscanf(f, "%lf", &mhz) != 1)
		mhz = 0.0;
	mhz /= 1000;
	fclose(f);
	return mhz;
}

int decode_tsc_forced(char **buf, double mhz, u64 tsc)
{
	return fmt_tsc(buf, tsc, mhz);
}

static int deep_sleep_states(int cpu)
{
	int ret;
	char *fn;
	FILE *f;
	char *line = NULL;
	size_t linelen = 0;

	/* When cpuidle is active assume there are deep sleep states */
	xasprintf(&fn, "/sys/devices/system/cpu/cpu%d/cpuidle", cpu);
	ret = access(fn, X_OK);
	free(fn);
	if (ret == 0)
		return 1;

	xasprintf(&fn, "/proc/acpi/processor/CPU%d/power", cpu);
	f = fopen(fn, "r");
	free(fn);
	if (!f)
		return 0;

	while ((getline(&line, &linelen, f)) > 0) {
		int n;
		if ((sscanf(line, " C%d:", &n)) == 1) {
			if (n > 1) {
				char *p = strstr(line, "usage");
				if (p && sscanf(p, "usage[%d]", &n) == 1 && n > 0)
					return 1;					
			}
		}
	}
	free(line);
	fclose(f);
	return 0;
}

/* Try to figure out if this CPU has a somewhat reliable TSC clock */
static int tsc_reliable(int cputype, int cpunum)
{
	if (!processor_flags)
		return 0;
	/* Trust the kernel */
	if (strstr(processor_flags, "nonstop_tsc"))
		return 1;
	/* TSC does not change frequency TBD: really old kernels don't set that */
	if (!strstr(processor_flags, "constant_tsc"))
		return 0;	
	/* We don't know the frequency on non Intel CPUs because the
	   kernel doesn't report them (e.g. AMD GH TSC doesn't run at highest
	   P-state). But then the kernel can just report the real time too. 
	   Also a lot of AMD and VIA CPUs have unreliable TSC, so would
	   need special rules here too. */
	if (!is_intel_cpu(cputype))
		return 0;
	if (deep_sleep_states(cpunum) && cputype < CPU_NEHALEM)
		return 0;
	return 1;
}

int decode_tsc_current(char **buf, int cpunum, enum cputype cputype, double mhz, 
		       unsigned long long tsc)
{
	double cmhz;
	if (!tsc_reliable(cputype, cpunum))
		return -1;
	cmhz = cpufreq_mhz(cpunum, mhz);
	if (cmhz != 0.0)
		mhz = cmhz;
	return fmt_tsc(buf, tsc, mhz);
}

#ifdef STANDALONE
int is_intel_cpu(int cpu) { return 1; }
/* claim this TSC is reliable always */
char *processor_flags = "nonstop_tsc";

static inline u64 rdtscll(void)
{
	unsigned a,b;
	asm volatile("rdtsc" : "=a" (a), "=d" (b));
	return (u64)a | (((u64)b) << 32);
}

int main(void)
{
	char *buf;
	u64 tsc = rdtscll();
	printf("%llx tsc\n", tsc);
	if (decode_tsc_current(&buf, 0, CPU_CORE2, 0.0, tsc) >= 0)
		printf("%s\n", buf);
	else
		printf("failed\n");
	return 0;
}
#endif
