So for more than a decade, the rdtsc instruction has in theory been
the fastest most accurate way to measure elapsed time on x86. Except
when it doesn't quite work.
The main issues are 1) frequency changing as a result of power
management, and 2) different values on different CPUs in SMP systems.
But this shouldn't prevent us from using it where we can!
The implementation below takes a rather simple approach to identifying
systems where rdtsc is reliable. Intel Core processors only. All
processors in the Core line run the cycle counter at the full nominal
speed of the cpu. (There is a feature flag to test for this, but ...)
Also, all the cores in a Core cpu have identical cycle counters, so it
doesn't matter who reads it in SMP configs. Multi-processor Core rigs
are not supported, you have to buy Xeons to get that.
The Atom line should also work, but I'm not running OpenBSD on my Atom
to check. Some more sophisticated logic to detect the feature flag
could also be used, such that this could work on Xeons, but then we
also need to check cpu topology to ensure there's only one package.
I'm not positive what the situation with AMD is. The new CPUs are
fixed frequency, but I don't know how many different cycle counters
there are in a multi-core/module/package setup. For now, it seems a
simple whitelist is the easiest approach.
Index: conf/files.i386
===================================================================
RCS file: /cvs/src/sys/arch/i386/conf/files.i386,v
retrieving revision 1.210
diff -u -p -r1.210 files.i386
--- conf/files.i386 27 May 2012 12:24:33 -0000 1.210
+++ conf/files.i386 15 Aug 2012 16:31:03 -0000
@@ -43,6 +43,7 @@ file arch/i386/i386/trap.c
file arch/i386/i386/vm_machdep.c
file arch/i386/i386/softintr.c
file arch/i386/i386/dkcsum.c bios
+file arch/i386/i386/rdtsc_tc.c !small_kernel
file dev/cninit.c
file arch/i386/i386/mptramp.s multiprocessor
file arch/i386/i386/mp_setperf.c multiprocessor
Index: include/cpu.h
===================================================================
RCS file: /cvs/src/sys/arch/i386/include/cpu.h,v
retrieving revision 1.122
diff -u -p -r1.122 cpu.h
--- include/cpu.h 27 Mar 2012 06:44:01 -0000 1.122
+++ include/cpu.h 15 Aug 2012 16:49:33 -0000
@@ -274,6 +274,8 @@ struct timeval;
*/
void calibrate_cyclecounter(void);
+void rdtsc_attach_tc(void);
+
/*
* pull in #defines for kinds of processors
*/
Index: i386/machdep.c
===================================================================
RCS file: /cvs/src/sys/arch/i386/i386/machdep.c,v
retrieving revision 1.510
diff -u -p -r1.510 machdep.c
--- i386/machdep.c 23 May 2012 08:23:43 -0000 1.510
+++ i386/machdep.c 15 Aug 2012 16:48:15 -0000
@@ -1820,6 +1820,9 @@ identifycpu(struct cpu_info *ci)
printf(" %d MHz", cpuspeed);
}
}
+#ifndef SMALL_KERNEL
+ rdtsc_attach_tc();
+#endif
}
if ((ci->ci_flags & CPUF_PRIMARY) == 0) {
printf("\n");
Index: i386/rdtsc_tc.c
===================================================================
RCS file: i386/rdtsc_tc.c
diff -N i386/rdtsc_tc.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ i386/rdtsc_tc.c 15 Aug 2012 17:02:17 -0000
@@ -0,0 +1,72 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 Ted Unangst <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/timetc.h>
+
+#include <machine/cpu.h>
+#include <machine/pctr.h>
+
+u_int rdtsc_get_timecount(struct timecounter *);
+
+struct timecounter rdtsc_timecounter = {
+ rdtsc_get_timecount, /* get_timecount */
+ NULL, /* no poll_pps */
+ 0xffffffff, /* counter_mask */
+ 0, /* frequency */
+ "rdtsc", /* name */
+ 9001 /* quality */
+};
+
+void
+rdtsc_attach_tc(void)
+{
+ static const char intelcore[] = "Intel(R) Core(TM)";
+ static int attached;
+
+ if (attached)
+ return;
+ attached = 1;
+
+ /*
+ * expedient way of testing for constant frequency and synced
+ * clocks between cores
+ */
+ if (strncmp(cpu_model, intelcore, sizeof(intelcore)-1) != 0)
+ return;
+
+ printf(" rdtsc");
+ rdtsc_timecounter.tc_frequency = cpuspeed;
+ tc_init(&rdtsc_timecounter);
+
+}
+
+u_int
+rdtsc_get_timecount(struct timecounter *tc)
+{
+ uint64_t tsc;
+
+ /* cpuspeed is scaled down, so for now, we do the same */
+ tsc = rdtsc();
+ tsc /= 1000000;
+ return (uint32_t)tsc;
+}
+