> Date: Wed, 15 Aug 2012 13:25:55 -0400
> From: Ted Unangst <[email protected]>
> 
> 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.

It's not obvious though that it is the most stable clock in the
system, even on systems where the TSC works correctly.  It's probably
good enough though.

> 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.

A whitelist-based approach is probably best.  What you could do is
attach this timecounter on platforms that are not whitelisted as well,
but give it a fairly low tc_quality if it isn't whitelisted.  That way
people can still select the TSC timecounter on systems where we fail
to whitelist it properly.

Using the brand string to the the whitelisting is a bad idea though in
my opinion.  The brand string is initialized by the BIOS, which
sometimes gets it wrong.  It is also under control of the marketing
idiots.

Using cpuspeed to set tc_frequency is a bad idea, since cpuspeed is
the actual frequency at which the CPU is running (which varies
depending on the power management state).

> 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;
> +}
> +

Reply via email to