blob: f79aaf074ff3e4034cff8127c750b26380418d8d [file] [log] [blame]
/* 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