freebsd-skq/sys/i386/isa/prof_machdep.c
bde c447676070 Added a sysctl (machdep.cputime_clock) to select the clock used by
"high resolution" profiling.  The available clocks are:
- the i8254 clock
- on non-SMP i586's and i686's: the TSC
- on systems with I586_PMC_GUPROF configured, and PERFMON configured
  and available: all the performance counters.
This is unfinshed (there are problems with locking out the PERFMON
device driver, and with losing calibration after switching the clock),
but better than static configuration or writing to kmem.

Changed ifdefs to avoid generating code for non-working option
combinations.
1997-11-24 18:16:23 +00:00

347 lines
8.5 KiB
C

/*-
* Copyright (c) 1996 Bruce D. Evans.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: prof_machdep.c,v 1.6 1997/02/22 09:36:59 peter Exp $
*/
#ifdef GUPROF
#include "opt_i586_guprof.h"
#include "opt_perfmon.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/gmon.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <machine/clock.h>
#include <machine/perfmon.h>
#include <machine/profile.h>
#endif
#ifdef PC98
#include <pc98/pc98/pc98.h>
#else
#include <i386/isa/isa.h>
#endif
#include <i386/isa/timerreg.h>
#ifdef GUPROF
#define CPUTIME_CLOCK_UNINITIALIZED 0
#define CPUTIME_CLOCK_I8254 1
#define CPUTIME_CLOCK_I586_CTR 2
#define CPUTIME_CLOCK_I586_PMC 3
#define CPUTIME_CLOCK_I8254_SHIFT 7
int cputime_bias = 1; /* initialize for locality of reference */
static int cputime_clock = CPUTIME_CLOCK_UNINITIALIZED;
#ifdef I586_PMC_GUPROF
static u_int cputime_clock_pmc_conf = I586_PMC_GUPROF;
static int cputime_clock_pmc_init;
static struct gmonparam saved_gmp;
#endif
#endif /* GUPROF */
#ifdef __GNUC__
asm("
GM_STATE = 0
GMON_PROF_OFF = 3
.text
.align 4,0x90
.globl __mcount
__mcount:
#
# Check that we are profiling. Do it early for speed.
#
cmpl $GMON_PROF_OFF,__gmonparam+GM_STATE
je Lmcount_exit
#
# __mcount is the same as mcount except the caller hasn't changed
# the stack except to call here, so the caller's raddr is above
# our raddr.
#
movl 4(%esp),%edx
jmp Lgot_frompc
.align 4,0x90
.globl mcount
mcount:
cmpl $GMON_PROF_OFF,__gmonparam+GM_STATE
je Lmcount_exit
#
# The caller's stack frame has already been built, so %ebp is
# the caller's frame pointer. The caller's raddr is in the
# caller's frame following the caller's caller's frame pointer.
#
movl 4(%ebp),%edx
Lgot_frompc:
#
# Our raddr is the caller's pc.
#
movl (%esp),%eax
pushfl
pushl %eax
pushl %edx
cli
call _mcount
addl $8,%esp
popfl
Lmcount_exit:
ret
");
#else /* !__GNUC__ */
#error
#endif /* __GNUC__ */
#ifdef GUPROF
/*
* mexitcount saves the return register(s), loads selfpc and calls
* mexitcount(selfpc) to do the work. Someday it should be in a machine
* dependent file together with cputime(), __mcount and mcount. cputime()
* can't just be put in machdep.c because it has to be compiled without -pg.
*/
#ifdef __GNUC__
asm("
.text
#
# Dummy label to be seen when gprof -u hides mexitcount.
#
.align 4,0x90
.globl __mexitcount
__mexitcount:
nop
GMON_PROF_HIRES = 4
.align 4,0x90
.globl mexitcount
mexitcount:
cmpl $GMON_PROF_HIRES,__gmonparam+GM_STATE
jne Lmexitcount_exit
pushl %edx
pushl %eax
movl 8(%esp),%eax
pushfl
pushl %eax
cli
call _mexitcount
addl $4,%esp
popfl
popl %eax
popl %edx
Lmexitcount_exit:
ret
");
#else /* !__GNUC__ */
#error
#endif /* __GNUC__ */
/*
* Return the time elapsed since the last call. The units are machine-
* dependent.
*/
int
cputime()
{
u_int count;
int delta;
#ifdef I586_PMC_GUPROF
u_quad_t event_count;
#endif
u_char high, low;
static u_int prev_count;
#if (defined(I586_CPU) || defined(I686_CPU)) && !defined(SMP)
if (cputime_clock == CPUTIME_CLOCK_I586_CTR) {
count = (u_int)rdtsc();
delta = (int)(count - prev_count);
prev_count = count;
return (delta);
}
#if defined(PERFMON) && defined(I586_PMC_GUPROF)
if (cputime_clock == CPUTIME_CLOCK_I586_PMC) {
/*
* XXX permon_read() should be inlined so that the
* perfmon module doesn't need to be compiled with
* profiling disabled and so that it is fast.
*/
perfmon_read(0, &event_count);
count = (u_int)event_count;
delta = (int)(count - prev_count);
prev_count = count;
return (delta);
}
#endif /* PERFMON && I586_PMC_GUPROF */
#endif /* (I586_CPU || I686_CPU) && !SMP */
/*
* Read the current value of the 8254 timer counter 0.
*/
outb(TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
low = inb(TIMER_CNTR0);
high = inb(TIMER_CNTR0);
count = ((high << 8) | low) << CPUTIME_CLOCK_I8254_SHIFT;
/*
* The timer counts down from TIMER_CNTR0_MAX to 0 and then resets.
* While profiling is enabled, this routine is called at least twice
* per timer reset (for mcounting and mexitcounting hardclock()),
* so at most one reset has occurred since the last call, and one
* has occurred iff the current count is larger than the previous
* count. This allows counter underflow to be detected faster
* than in microtime().
*/
delta = prev_count - count;
prev_count = count;
if ((int) delta <= 0)
return (delta + (timer0_max_count << CPUTIME_CLOCK_I8254_SHIFT));
return (delta);
}
static int
sysctl_machdep_cputime_clock SYSCTL_HANDLER_ARGS
{
int clock;
int event;
int error;
struct pmc pmc;
clock = cputime_clock;
#if defined(PERFMON) && defined(I586_PMC_GUPROF)
if (clock == CPUTIME_CLOCK_I586_PMC) {
pmc.pmc_val = cputime_clock_pmc_conf;
clock += pmc.pmc_event;
}
#endif
error = sysctl_handle_opaque(oidp, &clock, sizeof clock, req);
if (error == 0 && req->newptr != NULL) {
#if defined(PERFMON) && defined(I586_PMC_GUPROF)
if (clock >= CPUTIME_CLOCK_I586_PMC) {
event = clock - CPUTIME_CLOCK_I586_PMC;
if (event >= 256)
return (EINVAL);
pmc.pmc_num = 0;
pmc.pmc_event = event;
pmc.pmc_unit = 0;
pmc.pmc_flags = PMCF_E | PMCF_OS | PMCF_USR;
pmc.pmc_mask = 0;
cputime_clock_pmc_conf = pmc.pmc_val;
cputime_clock = CPUTIME_CLOCK_I586_PMC;
} else
#endif
{
if (clock < 0 || clock >= CPUTIME_CLOCK_I586_PMC)
return (EINVAL);
cputime_clock = clock;
}
}
return (error);
}
SYSCTL_PROC(_machdep, OID_AUTO, cputime_clock, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(u_int), sysctl_machdep_cputime_clock, "I", "");
/*
* The start and stop routines need not be here since we turn off profiling
* before calling them. They are here for convenience.
*/
void
startguprof(gp)
struct gmonparam *gp;
{
if (cputime_clock == CPUTIME_CLOCK_UNINITIALIZED) {
cputime_clock = CPUTIME_CLOCK_I8254;
#if (defined(I586_CPU) || defined(I686_CPU)) && !defined(SMP)
if (i586_ctr_freq != 0)
cputime_clock = CPUTIME_CLOCK_I586_CTR;
#endif
}
gp->profrate = timer_freq << CPUTIME_CLOCK_I8254_SHIFT;
#if (defined(I586_CPU) || defined(I686_CPU)) && !defined(SMP)
if (cputime_clock == CPUTIME_CLOCK_I586_CTR)
gp->profrate = i586_ctr_freq;
#if defined(PERFMON) && defined(I586_PMC_GUPROF)
else if (cputime_clock == CPUTIME_CLOCK_I586_PMC) {
if (perfmon_avail() &&
perfmon_setup(0, cputime_clock_pmc_conf) == 0) {
if (perfmon_start(0) != 0)
perfmon_fini(0);
else {
/* XXX 1 event == 1 us. */
gp->profrate = 1000000;
saved_gmp = *gp;
/* Zap overheads. They are invalid. */
gp->cputime_overhead = 0;
gp->mcount_overhead = 0;
gp->mcount_post_overhead = 0;
gp->mcount_pre_overhead = 0;
gp->mexitcount_overhead = 0;
gp->mexitcount_post_overhead = 0;
gp->mexitcount_pre_overhead = 0;
cputime_clock_pmc_init = TRUE;
}
}
}
#endif /* PERFMON && I586_PMC_GUPROF */
#endif /* (I586_CPU || I686_CPU) && !SMP */
cputime_bias = 0;
cputime();
}
void
stopguprof(gp)
struct gmonparam *gp;
{
#if defined(PERFMON) && defined(I586_PMC_GUPROF)
if (cputime_clock_pmc_init) {
*gp = saved_gmp;
perfmon_fini(0);
cputime_clock_pmc_init = FALSE;
}
#endif
}
#else /* !GUPROF */
#ifdef __GNUC__
asm("
.text
.align 4,0x90
.globl mexitcount
mexitcount:
ret
");
#else /* !__GNUC__ */
#error
#endif /* __GNUC__ */
#endif /* GUPROF */