From owner-p4-projects@FreeBSD.ORG Fri Mar 5 21:10:01 2010 Return-Path: Delivered-To: p4-projects@freebsd.org Received: by hub.freebsd.org (Postfix, from userid 32767) id 6D6C11065677; Fri, 5 Mar 2010 21:10:01 +0000 (UTC) Delivered-To: perforce@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 31163106564A for ; Fri, 5 Mar 2010 21:10:01 +0000 (UTC) (envelope-from jhb@freebsd.org) Received: from repoman.freebsd.org (repoman.freebsd.org [IPv6:2001:4f8:fff6::29]) by mx1.freebsd.org (Postfix) with ESMTP id 1D16A8FC0C for ; Fri, 5 Mar 2010 21:10:01 +0000 (UTC) Received: from repoman.freebsd.org (localhost [127.0.0.1]) by repoman.freebsd.org (8.14.3/8.14.3) with ESMTP id o25LA10Z049015 for ; Fri, 5 Mar 2010 21:10:01 GMT (envelope-from jhb@freebsd.org) Received: (from perforce@localhost) by repoman.freebsd.org (8.14.3/8.14.3/Submit) id o25LA1ST049013 for perforce@freebsd.org; Fri, 5 Mar 2010 21:10:01 GMT (envelope-from jhb@freebsd.org) Date: Fri, 5 Mar 2010 21:10:01 GMT Message-Id: <201003052110.o25LA1ST049013@repoman.freebsd.org> X-Authentication-Warning: repoman.freebsd.org: perforce set sender to jhb@freebsd.org using -f From: John Baldwin To: Perforce Change Reviews Precedence: bulk Cc: Subject: PERFORCE change 175383 for review X-BeenThere: p4-projects@freebsd.org X-Mailman-Version: 2.1.5 List-Id: p4 projects tree changes List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 05 Mar 2010 21:10:01 -0000 http://p4web.freebsd.org/chv.cgi?CH=175383 Change 175383 by jhb@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 #include #include +#include #include #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 #include +#ifdef __Linux__ #include #include +#endif +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#endif #include #include #include @@ -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 +#include +#include +#include +#endif #include #include #include @@ -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)