PERFORCE change 175383 for review
John Baldwin
jhb at FreeBSD.org
Fri Mar 5 21:10:01 UTC 2010
http://p4web.freebsd.org/chv.cgi?CH=175383
Change 175383 by jhb at jhb_jhbbsd on 2010/03/05 21:09:24
First set of patches to port mcelog(8) to FreeBSD. It can parse
MCA error messages from dmesg or /var/log/messages via --ascii
and it can also pull events from the running kernel via hw.mca
sysctls.
Known limitations, todo:
- The client/server stuff is not tested.
- The client will not work because it doesn't do a sendmsg() with
a SCM_CREDS control message.
- The server does not notice new events. On Linux /dev/mcelog
becomes readable when a new event is logged. For FreeBSD we
would need to poll hw.mca.count instead I think.
- Triggers are not currently supported. We would need to switch to
kqueue and use EVFILT_PROC events I think (or implement ppoll()).
- The code that tries to track bad pages is not supported.
Other changes:
- Install to /usr/local rather than /usr.
- Include a version of open_memstream(3) implemented via
funopen(3).
- Add a getline(3) stub using fgets(3).
- Use 'cpuid' instruction values to extract several things that
the Linux code reads out of /proc.
- Use FreeBSD-specific SCM_CREDS rather than Linux-specific
SCM_CREDENTIALS.
Affected files ...
.. //depot/projects/mcelog/Makefile#2 edit
.. //depot/projects/mcelog/cache.c#2 edit
.. //depot/projects/mcelog/client.c#2 edit
.. //depot/projects/mcelog/config.c#2 edit
.. //depot/projects/mcelog/eventloop.c#2 edit
.. //depot/projects/mcelog/intel.c#2 edit
.. //depot/projects/mcelog/mcelog.c#2 edit
.. //depot/projects/mcelog/mcelog.h#2 edit
.. //depot/projects/mcelog/memdb.c#2 edit
.. //depot/projects/mcelog/memstream.c#1 add
.. //depot/projects/mcelog/p4.c#2 edit
.. //depot/projects/mcelog/server.c#2 edit
.. //depot/projects/mcelog/tsc.c#2 edit
Differences ...
==== //depot/projects/mcelog/Makefile#2 (text) ====
@@ -1,5 +1,5 @@
CFLAGS := -g -Os
-prefix := /usr
+prefix := /usr/local
etcprefix :=
# Define appropiately for your distribution
# DOCDIR := /usr/share/doc/packages/mcelog
@@ -30,8 +30,14 @@
OBJ := p4.o k8.o mcelog.o dmi.o tsc.o core2.o bitfield.o intel.o \
nehalem.o dunnington.o tulsa.o config.o memutil.o msg.o \
- eventloop.o leaky-bucket.o memdb.o server.o trigger.o \
- client.o cache.o sysfs.o yellow.o page.o rbtree.o
+ eventloop.o leaky-bucket.o memdb.o server.o client.o \
+ cache.o rbtree.o
+ifndef FREEBSD
+OBJ += page.o trigger.o sysfs.o yellow.o
+endif
+ifdef FREEBSD
+OBJ += memstream.o
+endif
DISKDB_OBJ := diskdb.o dimm.o db.o
CLEAN := mcelog dmi tsc dbquery .depend .depend.X dbquery.o ${DISKDB_OBJ}
DOC := mce.pdf
==== //depot/projects/mcelog/cache.c#2 (text) ====
@@ -27,6 +27,7 @@
#include "sysfs.h"
#include "cache.h"
+#ifdef __Linux__
struct cache {
unsigned level;
/* Numerical values must match MCACOD */
@@ -164,6 +165,15 @@
Wprintf("Cannot find sysfs cache for CPU %d", cpu);
return -1;
}
+#endif
+
+#ifdef __FreeBSD__
+int cache_to_cpus(int cpu, unsigned level, unsigned type,
+ int *cpulen, unsigned **cpumap)
+{
+ return -1;
+}
+#endif
#ifdef TEST
main()
==== //depot/projects/mcelog/client.c#2 (text) ====
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <string.h>
#include <unistd.h>
#include "mcelog.h"
#include "client.h"
@@ -48,6 +49,9 @@
sizeof(struct sockaddr_un)) < 0)
SYSERRprintf("client connect");
+#ifdef __FreeBSD__
+ /* XXX: Need to use sendmsg() to send a SCM_CREDS control message. */
+#endif
n = strlen(command);
if (write(fd, command, n) != n)
SYSERRprintf("client command write");
==== //depot/projects/mcelog/config.c#2 (text) ====
@@ -126,6 +126,24 @@
return s;
}
+#ifdef __FreeBSD__
+/*
+ * Newer versions do have getline(), so this should use a version test
+ * at some point.
+ */
+static ssize_t getline(char **cp, size_t *lenp, FILE *f)
+{
+
+ if (*cp == NULL) {
+ *cp = malloc(4096);
+ *lenp = 4096;
+ }
+ if (fgets(*cp, *lenp, f) == NULL)
+ return (0);
+ return (strlen(*cp));
+}
+#endif
+
int parse_config_file(const char *fn)
{
FILE *f;
==== //depot/projects/mcelog/eventloop.c#2 (text) ====
@@ -38,7 +38,9 @@
static struct pollfd pollfds[MAX_POLLFD];
static struct pollcb pollcbs[MAX_POLLFD];
+#ifdef __Linux__
static sigset_t event_sigs;
+#endif
static int closeonexec(int fd)
{
@@ -97,6 +99,7 @@
}
/* Run signal handler only directly after event loop */
+#ifdef __Linux__
int event_signal(int sig)
{
static int first = 1;
@@ -111,11 +114,17 @@
return -1;
return 0;
}
+#endif
void eventloop(void)
{
for (;;) {
+#ifdef __Linux__
int n = ppoll(pollfds, max_pollfd, NULL, &event_sigs);
+#endif
+#ifdef __FreeBSD__
+ int n = poll(pollfds, max_pollfd, -1);
+#endif
if (n <= 0) {
if (n < 0 && errno != EINTR)
SYSERRprintf("poll error");
==== //depot/projects/mcelog/intel.c#2 (text) ====
@@ -79,7 +79,9 @@
corr_err_cnt = EXTRACT(m->status, 38, 52);
memory_error(m, channel, dimm, corr_err_cnt, recordlen);
+#ifdef __Linux__
account_page_error(m, channel, dimm, corr_err_cnt);
+#endif
return 1;
}
==== //depot/projects/mcelog/mcelog.c#2 (text) ====
@@ -20,8 +20,18 @@
#define _GNU_SOURCE 1
#include <sys/fcntl.h>
#include <sys/ioctl.h>
+#ifdef __Linux__
#include <asm/types.h>
#include <asm/ioctls.h>
+#endif
+#ifdef __FreeBSD__
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+#include <machine/mca.h>
+#include <err.h>
+#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@@ -59,7 +69,9 @@
enum cputype cputype = CPU_GENERIC;
+#ifdef __Linux__
char *logfn = LOG_DEV_FILENAME;
+#endif
int ignore_nodev;
int filter_bogus = 1;
@@ -70,7 +82,9 @@
int dump_raw_ascii;
int daemon_mode;
static char *inputfile;
+#ifdef __Linux__
char *processor_flags;
+#endif
static int foreground;
int filter_memory_errors;
static struct config_cred runcred = { .uid = -1U, .gid = -1U };
@@ -388,6 +402,7 @@
Wprintf("\n");
}
+#ifdef __Linux__
void check_cpu(void)
{
enum {
@@ -455,7 +470,44 @@
} else
Eprintf("warning: Cannot open /proc/cpuinfo\n");
}
+#endif
+
+#ifdef __FreeBSD__
+void check_cpu(void)
+{
+ char vendor[20];
+ u_int regs[4];
+ u_int cpu_id;
+ int family, model;
+ static int checked;
+
+ if (checked)
+ return;
+ checked = 1;
+
+ do_cpuid(0, regs);
+ ((u_int *)vendor)[0] = regs[1];
+ ((u_int *)vendor)[1] = regs[3];
+ ((u_int *)vendor)[2] = regs[2];
+ vendor[12] = 0;
+ do_cpuid(1, regs);
+ cpu_id = regs[0];
+ family = CPUID_TO_FAMILY(cpu_id);
+ model = CPUID_TO_MODEL(cpu_id);
+
+ if (cpu_forced)
+ ;
+ else if (!strcmp(vendor,"AuthenticAMD") &&
+ (family == 15 || family == 16 || family == 17))
+ cputype = CPU_K8;
+ else if (!strcmp(vendor,"GenuineIntel"))
+ cputype = select_intel_cputype(family, model);
+ /* Add checks for other CPUs here */
+}
+#endif
+
+#ifdef __Linux__
static char *skipspace(char *s)
{
while (isspace(*s))
@@ -479,6 +531,7 @@
}
return skipspace(s);
}
+#endif
static void dump_mce_final(struct mce *m, char *symbol, int missing, int recordlen,
int dseen)
@@ -501,6 +554,7 @@
if (recordlen < endof_field(struct mce, f)) \
recordlen = endof_field(struct mce, f)
+#ifdef __Linux__
/* Decode ASCII input for fatal messages */
static void decodefatal(FILE *inf)
{
@@ -646,7 +700,148 @@
if (data)
dump_mce_final(&m, symbol, missing, recordlen, disclaimer_seen);
}
+#endif
+#ifdef __FreeBSD__
+/* Used to map cpuid vendor strings to Linux cpuvendor values. */
+static struct {
+ char *name;
+ u_char cpuvendor;
+} vendor_ids[] = {
+ { "GenuineIntel", 0 },
+ { "CyrixInstead", 1 },
+ { "AuthenticAMD", 2 },
+ { "UMC UMC UMC ", 3 },
+ { "CentaurHauls", 5 },
+ { "GenuineTMx86", 7 },
+ { "Geode by NSC", 8 },
+};
+
+/* Convert FreeBSD's struct mca_record into a struct mce. */
+static void convert_mca(struct mca_record *mr, struct mce *mce, int live)
+{
+ memset(mce, 0, sizeof(*mce));
+ mce->status = mr->mr_status;
+ mce->misc = mr->mr_misc;
+ mce->addr = mr->mr_addr;
+ mce->tsc = mr->mr_tsc;
+ mce->bank = mr->mr_bank;
+ mce->finished = 1;
+ mce->apicid = mr->mr_apic_id;
+
+ /*
+ * For live records (from sysctl), fill in some fields using
+ * registers from the current CPU.
+ */
+ if (live) {
+ char vendor[20];
+ u_int i, regs[4];
+
+ do_cpuid(0, regs);
+ ((u_int *)vendor)[0] = regs[1];
+ ((u_int *)vendor)[1] = regs[3];
+ ((u_int *)vendor)[2] = regs[2];
+ vendor[12] = 0;
+ mce->cpuvendor = 0xff;
+ for (i = 0; i < sizeof(vendor_ids) / sizeof(vendor_ids[0]); i++)
+ if (strcmp(vendor, vendor_ids[i].name) == 0)
+ mce->cpuvendor = vendor_ids[i].cpuvendor;
+
+ do_cpuid(1, regs);
+ mce->cpuid = regs[0];
+ //mce->mcgcap = rdmsr(MSR_MCG_CAP);
+ }
+}
+
+/* Decode ASCII input for fatal messages */
+static void decodefatal(FILE *inf)
+{
+ struct mca_record mr;
+ struct mce m;
+ long long val;
+ char line[100], *s, symbol[1];
+ int data, missing;
+ enum rows {
+ BANK = 1,
+ CPU = 2,
+ ADDR = 4,
+ MISC = 8,
+ };
+
+ symbol[0] = '\0';
+ data = 0;
+ missing = 0;
+ memset(&mr, 0, sizeof(mr));
+ while ((s = fgets(line, sizeof(line), inf)) != NULL) {
+ s = strstr(s, "MCA: ");
+ if (s == NULL)
+ continue;
+ s += strlen("MCA: ");
+
+ if (strncmp(s, "bank", 4) == 0) {
+ /* Start of a new record, dump the previous one. */
+ if (data != 0) {
+ /* Require some minimum data. */
+ if (data & BANK) {
+ if (mr.mr_status & MC_STATUS_ADDRV &&
+ !(data & ADDR))
+ missing = 1;
+ if (mr.mr_status & MC_STATUS_MISCV &&
+ !(data & MISC))
+ missing = 1;
+ convert_mca(&mr, &m, 0);
+ dump_mce_final(&m, symbol, missing,
+ sizeof(struct mce), 0);
+ }
+ data = 0;
+ missing = 0;
+ memset(&mr, 0, sizeof(mr));
+ }
+
+ if (sscanf(s, "bank %d, status 0x%llx", &mr.mr_bank,
+ &val) != 2)
+ missing = 1;
+ else {
+ data |= BANK;
+ mr.mr_status = val;
+ }
+ }
+ if (strncmp(s, "CPU", 3) == 0) {
+ if (sscanf(s, "CPU %d ", &mr.mr_apic_id) != 1)
+ missing = 1;
+ else
+ data |= CPU;
+ }
+ if (strncmp(s, "Address", 7) == 0) {
+ if (sscanf(s, "Address 0x%llx", &val) != 1)
+ missing = 1;
+ else {
+ data |= ADDR;
+ mr.mr_addr = val;
+ }
+ }
+ if (strncmp(s, "Misc", 4) == 0) {
+ if (sscanf(s, "Misc 0x%llx", &val) != 1)
+ missing = 1;
+ else {
+ data |= MISC;
+ mr.mr_misc = val;
+ }
+ }
+ }
+
+ /* Dump the last record. */
+ if (data & BANK) {
+ if (mr.mr_status & MC_STATUS_ADDRV && !(data & ADDR))
+ missing = 1;
+ if (mr.mr_status & MC_STATUS_MISCV && !(data & MISC))
+ missing = 1;
+ convert_mca(&mr, &m, 0);
+ dump_mce_final(&m, symbol, missing, sizeof(struct mce), 0);
+ }
+}
+#endif
+
static void remove_pidfile(void)
{
unlink(pidfile);
@@ -900,8 +1095,10 @@
static void general_setup(void)
{
+#ifdef __Linux__
trigger_setup();
yellow_setup();
+#endif
config_cred("global", "run-credentials", &runcred);
if (config_bool("global", "filter-memory-errors") == 1)
filter_memory_errors = 1;
@@ -924,6 +1121,7 @@
}
}
+#ifdef __Linux__
static void process(int fd, unsigned recordlen, unsigned loglen, char *buf)
{
int i;
@@ -964,6 +1162,51 @@
if (finish)
exit(0);
}
+#endif
+
+#ifdef __FreeBSD__
+static void process(int fd __unused, unsigned recordlen,
+ unsigned loglen, char *buf __unused)
+{
+ struct mca_record mr;
+ struct mce mce;
+ int mib[4];
+ size_t len;
+ int finish, i;
+
+ len = 4;
+ if (sysctlnametomib("hw.mca.records", mib, &len) < 0)
+ return;
+
+ finish = 0;
+ recordlen = sizeof(struct mce);
+ for (i = 0; i < (int)loglen; i++) {
+ mib[3] = i;
+ len = sizeof(mr);
+ if (sysctl(mib, 4, &mr, &len, NULL, 0) < 0) {
+ warn("sysctl(hw.mca.records.%d)", i);
+ continue;
+ }
+
+ convert_mca(&mr, &mce, 1);
+ mce_prepare(&mce);
+ if (numerrors > 0 && --numerrors == 0)
+ finish = 1;
+ if (!mce_filter(&mce, recordlen))
+ continue;
+ if (!dump_raw_ascii) {
+ disclaimer();
+ Wprintf("MCE %d\n", i);
+ dump_mce(&mce, recordlen);
+ } else
+ dump_mce_raw_ascii(&mce, recordlen);
+ flushlog();
+ }
+
+ if (finish)
+ exit(0);
+}
+#endif
static void noargs(int ac, char **av)
{
@@ -1022,12 +1265,28 @@
char *buf;
};
+#ifdef __Linux__
static void process_mcefd(struct pollfd *pfd, void *data)
{
struct mcefd_data *d = (struct mcefd_data *)data;
assert((pfd->revents & POLLIN) != 0);
process(pfd->fd, d->recordlen, d->loglen, d->buf);
}
+#endif
+
+#ifdef __FreeBSD__
+/* Fetch current MCA records using sysctls. */
+static void fetch_records_info(struct mcefd_data *d)
+{
+ size_t len;
+ int count;
+
+ memset(d, 0, sizeof(*d));
+ len = sizeof(count);
+ if (sysctlbyname("hw.mca.count", &count, &len, NULL, 0) == 0)
+ d->loglen = count;
+}
+#endif
int main(int ac, char **av)
{
@@ -1057,13 +1316,16 @@
} else if (opt == 0)
break;
}
+#ifdef __Linux__
if (av[optind])
logfn = av[optind++];
+#endif
if (av[optind])
usage();
checkdmi();
general_setup();
+#ifdef __Linux__
fd = open(logfn, O_RDONLY);
if (fd < 0) {
if (ignore_nodev)
@@ -1078,15 +1340,24 @@
err("MCE_GET_LOG_LEN");
d.buf = xalloc(d.recordlen * d.loglen);
+#endif
+#ifdef __FreeBSD__
+ fetch_records_info(&d);
+ fd = -1;
+#endif
if (daemon_mode) {
check_cpu();
prefill_memdb();
if (!do_dmi)
closedmi();
server_setup();
+#ifdef __Linux__
page_setup();
+#endif
drop_cred();
+#ifdef __Linux__
register_pollcb(fd, POLLIN, process_mcefd, &d);
+#endif
if (!foreground && daemon(0, need_stdout()) < 0)
err("daemon");
if (pidfile)
@@ -1095,7 +1366,9 @@
} else {
process(fd, d.recordlen, d.loglen, d.buf);
}
+#ifdef __Linux__
trigger_wait();
+#endif
exit(0);
}
==== //depot/projects/mcelog/mcelog.h#2 (text) ====
@@ -64,9 +64,11 @@
#define MCI_STATUS_ADDRV (1ULL<<58) /* addr reg. valid */
#define MCI_STATUS_PCC (1ULL<<57) /* processor context corrupt */
+#ifndef MCG_STATUS_RIPV
#define MCG_STATUS_RIPV (1ULL<<0) /* restart ip valid */
#define MCG_STATUS_EIPV (1ULL<<1) /* eip points to correct instruction */
#define MCG_STATUS_MCIP (1ULL<<2) /* machine check in progress */
+#endif
#define MCG_CMCI_P (1ULL<<10) /* CMCI supported */
#define MCG_TES_P (1ULL<<11) /* Yellow bit cache threshold supported */
@@ -89,6 +91,10 @@
#define PRINTFLIKE
#endif
+#if defined(__FreeBSD__) && defined(_STDIO_H_)
+FILE *open_memstream(char **cp, size_t *lenp);
+#endif
+
int Wprintf(char *fmt, ...) PRINTFLIKE;
void Eprintf(char *fmt, ...) PRINTFLIKE;
void SYSERRprintf(char *fmt, ...) PRINTFLIKE;
==== //depot/projects/mcelog/memdb.c#2 (text) ====
@@ -170,7 +170,9 @@
asprintf(&env[ei++], "THRESHOLD_COUNT=%d", bucket->count + bucket->excess);
env[ei] = NULL;
assert(ei < MAX_ENV);
+#ifdef __Linux__
run_trigger(bc->trigger, NULL, env);
+#endif
for (i = 0; i < ei; i++)
free(env[i]);
out:
==== //depot/projects/mcelog/p4.c#2 (text) ====
@@ -175,8 +175,10 @@
Wprintf("%s CACHE %s %s Error\n", type, level,
get_RRRR_str((mca & CACHE_RRRR_MASK) >>
CACHE_RRRR_SHIFT));
+#ifdef __Linux__
if (track == 2)
run_yellow_trigger(cpu, typenum, levelnum, type, level, socket);
+#endif
} else if (test_prefix(10, mca)) {
if (mca == 0x400)
Wprintf("Internal Timer error\n");
==== //depot/projects/mcelog/server.c#2 (text) ====
@@ -101,7 +101,9 @@
static void dispatch_pages(FILE *fh)
{
+#ifdef __Linux__
dump_page_errors(fh);
+#endif
fprintf(fh, "done\n");
}
@@ -137,6 +139,7 @@
Enomem();
}
+#ifdef __Linux__
/* check if client is allowed to access */
static int access_check(int fd, struct msghdr *msg)
{
@@ -162,6 +165,35 @@
sendstring(fd, "permission denied\n");
return -1;
}
+#endif
+
+#ifdef __FreeBSD__
+/* check if client is allowed to access */
+static int access_check(int fd, struct msghdr *msg)
+{
+ struct cmsghdr *cmsg;
+ struct cmsgcred *cr;
+
+ /* check credentials */
+ cmsg = CMSG_FIRSTHDR(msg);
+ if (cmsg == NULL ||
+ cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_CREDS) {
+ Eprintf("Did not receive credentials over client unix socket %p\n",
+ cmsg);
+ return -1;
+ }
+ cr = (struct cmsgcred *)CMSG_DATA(cmsg);
+ if (cr->cmcred_uid == 0 ||
+ (acc.uid != -1U && cr->cmcred_uid == acc.uid) ||
+ (acc.gid != -1U && cr->cmcred_gid == acc.gid))
+ return 0;
+ Eprintf("rejected client access from pid:%u uid:%u gid:%u\n",
+ cr->cmcred_pid, cr->cmcred_uid, cr->cmcred_gid);
+ sendstring(fd, "permission denied\n");
+ return -1;
+}
+#endif
/* retrieve commands from client */
static int client_input(int fd, struct clientcon *cc)
@@ -242,18 +274,22 @@
{
struct clientcon *cc = NULL;
int nfd = accept(pfd->fd, NULL, 0);
+#ifdef __Linux__
int on;
+#endif
if (nfd < 0) {
SYSERRprintf("accept failed on client socket");
return;
}
+#ifdef __Linux__
on = 1;
if (setsockopt(nfd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
SYSERRprintf("Cannot enable credentials passing on client socket");
goto cleanup;
}
+#endif
cc = xalloc(sizeof(struct clientcon));
if (register_pollcb(nfd, POLLIN, client_event, cc) < 0) {
==== //depot/projects/mcelog/tsc.c#2 (text) ====
@@ -15,6 +15,12 @@
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
+#ifdef __FreeBSD__
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
@@ -46,6 +52,7 @@
return 0;
}
+#ifdef __Linux__
static double cpufreq_mhz(int cpu, double infomhz)
{
double mhz;
@@ -68,12 +75,29 @@
fclose(f);
return mhz;
}
+#endif
+
+#ifdef __FreeBSD__
+static double cpufreq_mhz(int cpu, double infomhz)
+{
+ double mhz;
+ uint64_t freq;
+ size_t len;
+
+ len = sizeof(freq);
+ if (sysctlbyname("machdep.tsc_freq", &freq, &len, NULL, 0) < 0)
+ return infomhz;
+ mhz = freq / 1000000.0;
+ return mhz;
+}
+#endif
int decode_tsc_forced(char **buf, double mhz, u64 tsc)
{
return fmt_tsc(buf, tsc, mhz);
}
+#ifdef __Linux__
static int deep_sleep_states(int cpu)
{
int ret;
@@ -132,6 +156,41 @@
return 0;
return 1;
}
+#endif
+
+#ifdef __FreeBSD__
+/* Try to figure out if this CPU has a somewhat reliable TSC clock */
+static int tsc_reliable(int cputype, int cpunum)
+{
+ u_int regs[4];
+ u_int cpu_id, amd_pminfo;
+
+ if (cputype != CPU_K8 && !is_intel_cpu(cputype))
+ return 0;
+
+ do_cpuid(0, regs);
+ cpu_id = regs[1];
+ do_cpuid(0x80000000, regs);
+ if (regs[0] >= 0x80000007) {
+ do_cpuid(0x80000007, regs);
+ amd_pminfo = regs[3];
+ } else
+ amd_pminfo = 0;
+
+ if (amd_pminfo & AMDPM_TSC_INVARIANT)
+ return 1;
+ if (is_intel_cpu(cputype)) {
+ if (CPUID_TO_FAMILY(cpu_id) >= 0x10 ||
+ cpu_id == 0x60fb2)
+ return 1;
+ } else if ((CPUID_TO_FAMILY(cpu_id) == 0x6 &&
+ CPUID_TO_MODEL(cpu_id) >= 0xe) ||
+ (CPUID_TO_FAMILY(cpu_id) == 0xf && CPUID_TO_MODEL(cpu_id) >= 0x3))
+ return 1;
+
+ return 0;
+}
+#endif
int decode_tsc_current(char **buf, int cpunum, enum cputype cputype, double mhz,
unsigned long long tsc)
More information about the p4-projects
mailing list