summaryrefslogtreecommitdiffstats
path: root/sys/x86
diff options
context:
space:
mode:
authormav <mav@FreeBSD.org>2010-06-20 21:33:29 +0000
committermav <mav@FreeBSD.org>2010-06-20 21:33:29 +0000
commitd1175426d7bcb8aeacdb7416e281e63e668da297 (patch)
tree0950027f8a4d7f49ad3b3ae792b390e08763c5c5 /sys/x86
parent75f3660605fcdf678edb8498311bba6ec652af82 (diff)
downloadFreeBSD-src-d1175426d7bcb8aeacdb7416e281e63e668da297.zip
FreeBSD-src-d1175426d7bcb8aeacdb7416e281e63e668da297.tar.gz
Implement new event timers infrastructure. It provides unified APIs for
writing event timer drivers, for choosing best possible drivers by machine independent code and for operating them to supply kernel with hardclock(), statclock() and profclock() events in unified fashion on various hardware. Infrastructure provides support for both per-CPU (independent for every CPU core) and global timers in periodic and one-shot modes. MI management code at this moment uses only periodic mode, but one-shot mode use planned for later, as part of tickless kernel project. For this moment infrastructure used on i386 and amd64 architectures. Other archs are welcome to follow, while their current operation should not be affected. This patch updates existing drivers (i8254, RTC and LAPIC) for the new order, and adds event timers support into the HPET driver. These drivers have different capabilities: LAPIC - per-CPU timer, supports periodic and one-shot operation, may freeze in C3 state, calibrated on first use, so may be not exactly precise. HPET - depending on hardware can work as per-CPU or global, supports periodic and one-shot operation, usually provides several event timers. i8254 - global, limited to periodic mode, because same hardware used also as time counter. RTC - global, supports only periodic mode, set of frequencies in Hz limited by powers of 2. Depending on hardware capabilities, drivers preferred in following orders, either LAPIC, HPETs, i8254, RTC or HPETs, LAPIC, i8254, RTC. User may explicitly specify wanted timers via loader tunables or sysctls: kern.eventtimer.timer1 and kern.eventtimer.timer2. If requested driver is unavailable or unoperational, system will try to replace it. If no more timers available or "NONE" specified for second, system will operate using only one timer, multiplying it's frequency by few times and uing respective dividers to honor hz, stathz and profhz values, set during initial setup.
Diffstat (limited to 'sys/x86')
-rw-r--r--sys/x86/isa/atrtc.c143
-rw-r--r--sys/x86/isa/clock.c334
-rw-r--r--sys/x86/x86/local_apic.c213
-rw-r--r--sys/x86/x86/timeevents.c509
4 files changed, 845 insertions, 354 deletions
diff --git a/sys/x86/isa/atrtc.c b/sys/x86/isa/atrtc.c
index 777c720..a7cd7f1 100644
--- a/sys/x86/isa/atrtc.c
+++ b/sys/x86/isa/atrtc.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 2008 Poul-Henning Kamp
+ * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -39,18 +40,21 @@ __FBSDID("$FreeBSD$");
#include <sys/mutex.h>
#include <sys/kernel.h>
#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/rman.h>
+#include <sys/timeet.h>
#include <isa/rtc.h>
#ifdef DEV_ISA
#include <isa/isareg.h>
#include <isa/isavar.h>
#endif
+#include <machine/intr_machdep.h>
+#include "clock_if.h"
#define RTC_LOCK mtx_lock_spin(&clock_lock)
#define RTC_UNLOCK mtx_unlock_spin(&clock_lock)
-int atrtcclock_disable = 0;
-
static int rtc_reg = -1;
static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
static u_char rtc_statusb = RTCSB_24HR;
@@ -98,7 +102,7 @@ readrtc(int port)
return(bcd2bin(rtcin(port)));
}
-void
+static void
atrtc_start(void)
{
@@ -106,7 +110,7 @@ atrtc_start(void)
writertc(RTC_STATUSB, RTCSB_24HR);
}
-void
+static void
atrtc_rate(unsigned rate)
{
@@ -114,7 +118,7 @@ atrtc_rate(unsigned rate)
writertc(RTC_STATUSA, rtc_statusa);
}
-void
+static void
atrtc_enable_intr(void)
{
@@ -123,6 +127,15 @@ atrtc_enable_intr(void)
rtcin(RTC_INTR);
}
+static void
+atrtc_disable_intr(void)
+{
+
+ rtc_statusb &= ~RTCSB_PINTR;
+ writertc(RTC_STATUSB, rtc_statusb);
+ rtcin(RTC_INTR);
+}
+
void
atrtc_restore(void)
{
@@ -135,41 +148,74 @@ atrtc_restore(void)
rtcin(RTC_INTR);
}
-int
-atrtc_setup_clock(void)
-{
- int diag;
-
- if (atrtcclock_disable)
- return (0);
-
- diag = rtcin(RTC_DIAG);
- if (diag != 0) {
- printf("RTC BIOS diagnostic error %b\n",
- diag, RTCDG_BITS);
- return (0);
- }
-
- stathz = RTC_NOPROFRATE;
- profhz = RTC_PROFRATE;
-
- return (1);
-}
-
/**********************************************************************
* RTC driver for subr_rtc
*/
-#include "clock_if.h"
-
-#include <sys/rman.h>
-
struct atrtc_softc {
int port_rid, intr_rid;
struct resource *port_res;
struct resource *intr_res;
+ void *intr_handler;
+ struct eventtimer et;
};
+static int
+rtc_start(struct eventtimer *et,
+ struct bintime *first, struct bintime *period)
+{
+
+ atrtc_rate(max(fls((period->frac + (period->frac >> 1)) >> 32) - 17, 1));
+ atrtc_enable_intr();
+ return (0);
+}
+
+static int
+rtc_stop(struct eventtimer *et)
+{
+
+ atrtc_disable_intr();
+ return (0);
+}
+
+/*
+ * This routine receives statistical clock interrupts from the RTC.
+ * As explained above, these occur at 128 interrupts per second.
+ * When profiling, we receive interrupts at a rate of 1024 Hz.
+ *
+ * This does not actually add as much overhead as it sounds, because
+ * when the statistical clock is active, the hardclock driver no longer
+ * needs to keep (inaccurate) statistics on its own. This decouples
+ * statistics gathering from scheduling interrupts.
+ *
+ * The RTC chip requires that we read status register C (RTC_INTR)
+ * to acknowledge an interrupt, before it will generate the next one.
+ * Under high interrupt load, rtcintr() can be indefinitely delayed and
+ * the clock can tick immediately after the read from RTC_INTR. In this
+ * case, the mc146818A interrupt signal will not drop for long enough
+ * to register with the 8259 PIC. If an interrupt is missed, the stat
+ * clock will halt, considerably degrading system performance. This is
+ * why we use 'while' rather than a more straightforward 'if' below.
+ * Stat clock ticks can still be lost, causing minor loss of accuracy
+ * in the statistics, but the stat clock will no longer stop.
+ */
+static int
+rtc_intr(void *arg)
+{
+ struct atrtc_softc *sc = (struct atrtc_softc *)arg;
+ int flag = 0;
+
+ while (rtcin(RTC_INTR) & RTCIR_PERIOD) {
+ flag = 1;
+ if (sc->et.et_active) {
+ sc->et.et_event_cb(&sc->et,
+ sc->et.et_arg ? sc->et.et_arg :
+ curthread->td_intr_frame);
+ }
+ }
+ return(flag ? FILTER_HANDLED : FILTER_STRAY);
+}
+
/*
* Attach to the ISA PnP descriptors for the timer and realtime clock.
*/
@@ -196,23 +242,42 @@ static int
atrtc_attach(device_t dev)
{
struct atrtc_softc *sc;
- int i;
+ int i, diag, haveirq = 0;
- /*
- * Not that we need them or anything, but grab our resources
- * so they show up, correctly attributed, in the big picture.
- */
-
sc = device_get_softc(dev);
if (!(sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT,
&sc->port_rid, IO_RTC, IO_RTC + 1, 2, RF_ACTIVE)))
device_printf(dev,"Warning: Couldn't map I/O.\n");
if (!(sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ,
&sc->intr_rid, 8, 8, 1, RF_ACTIVE)))
- device_printf(dev,"Warning: Couldn't map Interrupt.\n");
+ device_printf(dev,"Couldn't map Interrupt.\n");
+ else if ((bus_setup_intr(dev, sc->intr_res,
+ INTR_MPSAFE | INTR_TYPE_CLK, (driver_filter_t *)rtc_intr, NULL,
+ sc, &sc->intr_handler))) {
+ device_printf(dev, "Can't setup interrupt.\n");
+ } else {
+ haveirq = 1;
+ /* Bind IRQ to BSP to avoid live migration. */
+ bus_bind_intr(dev, sc->intr_res, 0);
+ }
+ diag = rtcin(RTC_DIAG);
+ if (diag != 0)
+ printf("RTC BIOS diagnostic error %b\n", diag, RTCDG_BITS);
+ atrtc_start();
clock_register(dev, 1000000);
- if (resource_int_value("atrtc", 0, "clock", &i) == 0 && i == 0)
- atrtcclock_disable = 1;
+ bzero(&sc->et, sizeof(struct eventtimer));
+ if (haveirq &&
+ (resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "clock", &i) != 0 || i != 0)) {
+ sc->et.et_name = "RTC";
+ sc->et.et_flags = ET_FLAGS_PERIODIC;
+ sc->et.et_quality = 0;
+ sc->et.et_frequency = 32768;
+ sc->et.et_start = rtc_start;
+ sc->et.et_stop = rtc_stop;
+ sc->et.et_priv = dev;
+ et_register(&sc->et);
+ }
return(0);
}
diff --git a/sys/x86/isa/clock.c b/sys/x86/isa/clock.c
index b0d49d5..5929f5b 100644
--- a/sys/x86/isa/clock.c
+++ b/sys/x86/isa/clock.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 1990 The Regents of the University of California.
+ * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org>
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
@@ -39,11 +40,7 @@ __FBSDID("$FreeBSD$");
* Routines to handle clock hardware.
*/
-#ifndef __amd64__
-#include "opt_apic.h"
-#endif
#include "opt_clock.h"
-#include "opt_kdtrace.h"
#include "opt_isa.h"
#include "opt_mca.h"
@@ -54,21 +51,20 @@ __FBSDID("$FreeBSD$");
#include <sys/kdb.h>
#include <sys/mutex.h>
#include <sys/proc.h>
-#include <sys/timetc.h>
#include <sys/kernel.h>
#include <sys/module.h>
+#include <sys/rman.h>
#include <sys/sched.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
+#include <sys/timeet.h>
+#include <sys/timetc.h>
#include <machine/clock.h>
#include <machine/cpu.h>
#include <machine/intr_machdep.h>
-#include <machine/md_var.h>
-#include <machine/apicvar.h>
#include <machine/ppireg.h>
#include <machine/timerreg.h>
-#include <machine/smp.h>
#include <isa/rtc.h>
#ifdef DEV_ISA
@@ -80,12 +76,6 @@ __FBSDID("$FreeBSD$");
#include <i386/bios/mca_machdep.h>
#endif
-#ifdef KDTRACE_HOOKS
-#include <sys/dtrace_bsd.h>
-#endif
-
-#define TIMER_DIV(x) ((i8254_freq + (x) / 2) / (x))
-
int clkintr_pending;
#ifndef TIMER_FREQ
#define TIMER_FREQ 1193182
@@ -95,17 +85,23 @@ TUNABLE_INT("hw.i8254.freq", &i8254_freq);
int i8254_max_count;
static int i8254_real_max_count;
-static int lapic_allclocks = 1;
-TUNABLE_INT("machdep.lapic_allclocks", &lapic_allclocks);
-
struct mtx clock_lock;
static struct intsrc *i8254_intsrc;
-static u_int32_t i8254_lastcount;
-static u_int32_t i8254_offset;
+static uint16_t i8254_lastcount;
+static uint16_t i8254_offset;
static int (*i8254_pending)(struct intsrc *);
static int i8254_ticked;
-static int using_atrtc_timer;
-static enum lapic_clock using_lapic_timer = LAPIC_CLOCK_NONE;
+
+struct attimer_softc {
+ int intr_en;
+ int intr_rid;
+ struct resource *intr_res;
+ void *intr_handler;
+ struct timecounter tc;
+ struct eventtimer et;
+ uint32_t intr_period;
+};
+static struct attimer_softc *attimer_sc = NULL;
/* Values for timerX_state: */
#define RELEASED 0
@@ -116,39 +112,14 @@ static enum lapic_clock using_lapic_timer = LAPIC_CLOCK_NONE;
static u_char timer2_state;
static unsigned i8254_get_timecount(struct timecounter *tc);
-static unsigned i8254_simple_get_timecount(struct timecounter *tc);
-static void set_i8254_freq(u_int freq, int intr_freq);
-
-static struct timecounter i8254_timecounter = {
- i8254_get_timecount, /* get_timecount */
- 0, /* no poll_pps */
- ~0u, /* counter_mask */
- 0, /* frequency */
- "i8254", /* name */
- 0 /* quality */
-};
-
-int
-hardclockintr(struct trapframe *frame)
-{
-
- timer1clock(TRAPF_USERMODE(frame), TRAPF_PC(frame));
- return (FILTER_HANDLED);
-}
-
-int
-statclockintr(struct trapframe *frame)
-{
-
- timer2clock(TRAPF_USERMODE(frame), TRAPF_PC(frame));
- return (FILTER_HANDLED);
-}
+static void set_i8254_freq(u_int freq, uint32_t intr_period);
static int
-clkintr(struct trapframe *frame)
+clkintr(void *arg)
{
+ struct attimer_softc *sc = (struct attimer_softc *)arg;
- if (timecounter->tc_get_timecount == i8254_get_timecount) {
+ if (sc->intr_period != 0) {
mtx_lock_spin(&clock_lock);
if (i8254_ticked)
i8254_ticked = 0;
@@ -159,25 +130,11 @@ clkintr(struct trapframe *frame)
clkintr_pending = 0;
mtx_unlock_spin(&clock_lock);
}
- KASSERT(using_lapic_timer == LAPIC_CLOCK_NONE,
- ("clk interrupt enabled with lapic timer"));
-#ifdef KDTRACE_HOOKS
- /*
- * If the DTrace hooks are configured and a callback function
- * has been registered, then call it to process the high speed
- * timers.
- */
- int cpu = PCPU_GET(cpuid);
- if (cyclic_clock_func[cpu] != NULL)
- (*cyclic_clock_func[cpu])(frame);
-#endif
-
-#ifdef SMP
- if (smp_started)
- ipi_all_but_self(IPI_HARDCLOCK);
-#endif
- hardclockintr(frame);
+ if (sc && sc->et.et_active) {
+ sc->et.et_event_cb(&sc->et,
+ sc->et.et_arg ? sc->et.et_arg : curthread->td_intr_frame);
+ }
#ifdef DEV_MCA
/* Reset clock interrupt by asserting bit 7 of port 0x61 */
@@ -233,43 +190,6 @@ timer_spkr_setfreq(int freq)
mtx_unlock_spin(&clock_lock);
}
-/*
- * This routine receives statistical clock interrupts from the RTC.
- * As explained above, these occur at 128 interrupts per second.
- * When profiling, we receive interrupts at a rate of 1024 Hz.
- *
- * This does not actually add as much overhead as it sounds, because
- * when the statistical clock is active, the hardclock driver no longer
- * needs to keep (inaccurate) statistics on its own. This decouples
- * statistics gathering from scheduling interrupts.
- *
- * The RTC chip requires that we read status register C (RTC_INTR)
- * to acknowledge an interrupt, before it will generate the next one.
- * Under high interrupt load, rtcintr() can be indefinitely delayed and
- * the clock can tick immediately after the read from RTC_INTR. In this
- * case, the mc146818A interrupt signal will not drop for long enough
- * to register with the 8259 PIC. If an interrupt is missed, the stat
- * clock will halt, considerably degrading system performance. This is
- * why we use 'while' rather than a more straightforward 'if' below.
- * Stat clock ticks can still be lost, causing minor loss of accuracy
- * in the statistics, but the stat clock will no longer stop.
- */
-static int
-rtcintr(struct trapframe *frame)
-{
- int flag = 0;
-
- while (rtcin(RTC_INTR) & RTCIR_PERIOD) {
- flag = 1;
-#ifdef SMP
- if (smp_started)
- ipi_all_but_self(IPI_STATCLOCK);
-#endif
- statclockintr(frame);
- }
- return(flag ? FILTER_HANDLED : FILTER_STRAY);
-}
-
static int
getit(void)
{
@@ -405,17 +325,18 @@ DELAY(int n)
}
static void
-set_i8254_freq(u_int freq, int intr_freq)
+set_i8254_freq(u_int freq, uint32_t intr_period)
{
int new_i8254_real_max_count;
- i8254_timecounter.tc_frequency = freq;
mtx_lock_spin(&clock_lock);
i8254_freq = freq;
- if (using_lapic_timer != LAPIC_CLOCK_NONE)
+ if (intr_period == 0)
new_i8254_real_max_count = 0x10000;
- else
- new_i8254_real_max_count = TIMER_DIV(intr_freq);
+ else {
+ new_i8254_real_max_count =
+ min(((uint64_t)i8254_freq * intr_period) >> 32, 0x10000);
+ }
if (new_i8254_real_max_count != i8254_real_max_count) {
i8254_real_max_count = new_i8254_real_max_count;
if (i8254_real_max_count == 0x10000)
@@ -465,113 +386,22 @@ i8254_init(void)
{
mtx_init(&clock_lock, "clk", NULL, MTX_SPIN | MTX_NOPROFILE);
- set_i8254_freq(i8254_freq, hz);
+ set_i8254_freq(i8254_freq, 0);
}
void
startrtclock()
{
- atrtc_start();
-
- set_i8254_freq(i8254_freq, hz);
- tc_init(&i8254_timecounter);
-
init_TSC();
}
-/*
- * Start both clocks running.
- */
void
-cpu_initclocks()
+cpu_initclocks(void)
{
-#if defined(__amd64__) || defined(DEV_APIC)
- enum lapic_clock tlsca;
-#endif
- int tasc;
-
- /* Initialize RTC. */
- atrtc_start();
- tasc = atrtc_setup_clock();
-
- /*
- * If the atrtc successfully initialized and the users didn't force
- * otherwise use the LAPIC in order to cater hardclock only, otherwise
- * take in charge all the clock sources.
- */
-#if defined(__amd64__) || defined(DEV_APIC)
- tlsca = (lapic_allclocks == 0 && tasc != 0) ? LAPIC_CLOCK_HARDCLOCK :
- LAPIC_CLOCK_ALL;
- using_lapic_timer = lapic_setup_clock(tlsca);
-#endif
- /*
- * If we aren't using the local APIC timer to drive the kernel
- * clocks, setup the interrupt handler for the 8254 timer 0 so
- * that it can drive hardclock(). Otherwise, change the 8254
- * timecounter to user a simpler algorithm.
- */
- if (using_lapic_timer == LAPIC_CLOCK_NONE) {
- timer1hz = hz;
- intr_add_handler("clk", 0, (driver_filter_t *)clkintr, NULL,
- NULL, INTR_TYPE_CLK, NULL);
- i8254_intsrc = intr_lookup_source(0);
- if (i8254_intsrc != NULL)
- i8254_pending =
- i8254_intsrc->is_pic->pic_source_pending;
- } else {
- i8254_timecounter.tc_get_timecount =
- i8254_simple_get_timecount;
- i8254_timecounter.tc_counter_mask = 0xffff;
- set_i8254_freq(i8254_freq, hz);
- }
-
- /*
- * If the separate statistics clock hasn't been explicility disabled
- * and we aren't already using the local APIC timer to drive the
- * kernel clocks, then setup the RTC to periodically interrupt to
- * drive statclock() and profclock().
- */
- if (using_lapic_timer != LAPIC_CLOCK_ALL) {
- using_atrtc_timer = tasc;
- if (using_atrtc_timer) {
- timer2hz = RTC_NOPROFRATE;
- /* Enable periodic interrupts from the RTC. */
- intr_add_handler("rtc", 8,
- (driver_filter_t *)rtcintr, NULL, NULL,
- INTR_TYPE_CLK, NULL);
- atrtc_enable_intr();
- } else {
- profhz = hz;
- if (hz < 128)
- stathz = hz;
- else
- stathz = hz / (hz / 128);
- timer2hz = 0;
- }
- }
init_TSC_tc();
-}
-
-void
-cpu_startprofclock(void)
-{
-
- if (using_lapic_timer == LAPIC_CLOCK_ALL || !using_atrtc_timer)
- return;
- atrtc_rate(RTCSA_PROF);
- timer2hz = RTC_PROFRATE;
-}
-
-void
-cpu_stopprofclock(void)
-{
-
- if (using_lapic_timer == LAPIC_CLOCK_ALL || !using_atrtc_timer)
- return;
- atrtc_rate(RTCSA_NOPROF);
- timer2hz = RTC_NOPROFRATE;
+ cpu_initclocks_bsp();
}
static int
@@ -586,8 +416,14 @@ sysctl_machdep_i8254_freq(SYSCTL_HANDLER_ARGS)
*/
freq = i8254_freq;
error = sysctl_handle_int(oidp, &freq, 0, req);
- if (error == 0 && req->newptr != NULL)
- set_i8254_freq(freq, hz);
+ if (error == 0 && req->newptr != NULL) {
+ if (attimer_sc) {
+ set_i8254_freq(freq, attimer_sc->intr_period);
+ attimer_sc->tc.tc_frequency = freq;
+ } else {
+ set_i8254_freq(freq, 0);
+ }
+ }
return (error);
}
@@ -595,19 +431,17 @@ SYSCTL_PROC(_machdep, OID_AUTO, i8254_freq, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(u_int), sysctl_machdep_i8254_freq, "IU", "");
static unsigned
-i8254_simple_get_timecount(struct timecounter *tc)
-{
-
- return (i8254_max_count - getit());
-}
-
-static unsigned
i8254_get_timecount(struct timecounter *tc)
{
+ device_t dev = (device_t)tc->tc_priv;
+ struct attimer_softc *sc = device_get_softc(dev);
register_t flags;
- u_int count;
+ uint16_t count;
u_int high, low;
+ if (sc->intr_period == 0)
+ return (i8254_max_count - getit());
+
#ifdef __amd64__
flags = read_rflags();
#else
@@ -635,6 +469,33 @@ i8254_get_timecount(struct timecounter *tc)
return (count);
}
+static int
+attimer_start(struct eventtimer *et,
+ struct bintime *first, struct bintime *period)
+{
+ device_t dev = (device_t)et->et_priv;
+ struct attimer_softc *sc = device_get_softc(dev);
+
+ sc->intr_period = period->frac >> 32;
+ set_i8254_freq(i8254_freq, sc->intr_period);
+ if (!sc->intr_en) {
+ i8254_intsrc->is_pic->pic_enable_source(i8254_intsrc);
+ sc->intr_en = 1;
+ }
+ return (0);
+}
+
+static int
+attimer_stop(struct eventtimer *et)
+{
+ device_t dev = (device_t)et->et_priv;
+ struct attimer_softc *sc = device_get_softc(dev);
+
+ sc->intr_period = 0;
+ set_i8254_freq(i8254_freq, sc->intr_period);
+ return (0);
+}
+
#ifdef DEV_ISA
/*
* Attach to the ISA PnP descriptors for the timer
@@ -650,14 +511,53 @@ attimer_probe(device_t dev)
int result;
result = ISA_PNP_PROBE(device_get_parent(dev), dev, attimer_ids);
- if (result <= 0)
- device_quiet(dev);
return(result);
}
static int
attimer_attach(device_t dev)
{
+ struct attimer_softc *sc;
+ int i;
+
+ attimer_sc = sc = device_get_softc(dev);
+ bzero(sc, sizeof(struct attimer_softc));
+ if (!(sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ,
+ &sc->intr_rid, 0, 0, 1, RF_ACTIVE)))
+ device_printf(dev,"Warning: Couldn't map Interrupt.\n");
+ i8254_intsrc = intr_lookup_source(0);
+ if (i8254_intsrc != NULL)
+ i8254_pending = i8254_intsrc->is_pic->pic_source_pending;
+ set_i8254_freq(i8254_freq, 0);
+ sc->tc.tc_get_timecount = i8254_get_timecount;
+ sc->tc.tc_counter_mask = 0xffff;
+ sc->tc.tc_frequency = i8254_freq;
+ sc->tc.tc_name = "i8254";
+ sc->tc.tc_quality = 0;
+ sc->tc.tc_priv = dev;
+ tc_init(&sc->tc);
+ if (resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "clock", &i) != 0 || i != 0) {
+ /* Dirty hack, to make bus_setup_intr to not enable source. */
+ i8254_intsrc->is_handlers++;
+ if ((bus_setup_intr(dev, sc->intr_res,
+ INTR_MPSAFE | INTR_TYPE_CLK,
+ (driver_filter_t *)clkintr, NULL,
+ sc, &sc->intr_handler))) {
+ device_printf(dev, "Can't setup interrupt.\n");
+ } else {
+ i8254_intsrc->is_pic->pic_enable_intr(i8254_intsrc);
+ sc->et.et_name = "i8254";
+ sc->et.et_flags = ET_FLAGS_PERIODIC;
+ sc->et.et_quality = 100;
+ sc->et.et_frequency = i8254_freq;
+ sc->et.et_start = attimer_start;
+ sc->et.et_stop = attimer_stop;
+ sc->et.et_priv = dev;
+ et_register(&sc->et);
+ }
+ i8254_intsrc->is_handlers--;
+ }
return(0);
}
@@ -683,7 +583,7 @@ static device_method_t attimer_methods[] = {
static driver_t attimer_driver = {
"attimer",
attimer_methods,
- 1, /* no softc */
+ sizeof(struct attimer_softc),
};
static devclass_t attimer_devclass;
diff --git a/sys/x86/x86/local_apic.c b/sys/x86/x86/local_apic.c
index 7fec4f6..d1a8439 100644
--- a/sys/x86/x86/local_apic.c
+++ b/sys/x86/x86/local_apic.c
@@ -49,6 +49,7 @@ __FBSDID("$FreeBSD$");
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/smp.h>
+#include <sys/timeet.h>
#include <vm/vm.h>
#include <vm/pmap.h>
@@ -79,11 +80,6 @@ __FBSDID("$FreeBSD$");
#define GSEL_APIC GSEL(GCODE_SEL, SEL_KPL)
#endif
-#ifdef KDTRACE_HOOKS
-#include <sys/dtrace_bsd.h>
-cyclic_clock_func_t cyclic_clock_func[MAXCPU];
-#endif
-
/* Sanity checks on IDT vectors. */
CTASSERT(APIC_IO_INTS + APIC_NUM_IOINTS == APIC_TIMER_INT);
CTASSERT(APIC_TIMER_INT < APIC_LOCAL_INTS);
@@ -119,6 +115,8 @@ struct lapic {
u_int la_cluster_id:2;
u_int la_present:1;
u_long *la_timer_count;
+ u_long la_timer_period;
+ u_int la_timer_mode;
/* Include IDT_SYSCALL to make indexing easier. */
int la_ioint_irqs[APIC_NUM_IOINTS + 1];
} static lapics[MAX_APIC_ID + 1];
@@ -155,16 +153,20 @@ extern inthand_t IDTVEC(rsvd);
volatile lapic_t *lapic;
vm_paddr_t lapic_paddr;
-static u_long lapic_timer_divisor, lapic_timer_period, lapic_timer_hz;
-static enum lapic_clock clockcoverage;
+static u_long lapic_timer_divisor;
+static struct eventtimer lapic_et;
static void lapic_enable(void);
static void lapic_resume(struct pic *pic);
static void lapic_timer_enable_intr(void);
static void lapic_timer_oneshot(u_int count);
static void lapic_timer_periodic(u_int count);
+static void lapic_timer_stop(void);
static void lapic_timer_set_divisor(u_int divisor);
static uint32_t lvt_mode(struct lapic *la, u_int pin, uint32_t value);
+static int lapic_et_start(struct eventtimer *et,
+ struct bintime *first, struct bintime *period);
+static int lapic_et_stop(struct eventtimer *et);
struct pic lapic_pic = { .pic_resume = lapic_resume };
@@ -215,6 +217,8 @@ lvt_mode(struct lapic *la, u_int pin, uint32_t value)
void
lapic_init(vm_paddr_t addr)
{
+ u_int regs[4];
+ int i, arat;
/* Map the local APIC and setup the spurious interrupt handler. */
KASSERT(trunc_page(addr) == addr,
@@ -240,6 +244,30 @@ lapic_init(vm_paddr_t addr)
/* Local APIC CMCI. */
setidt(APIC_CMC_INT, IDTVEC(cmcint), SDT_APICT, SEL_KPL, GSEL_APIC);
+
+ if ((resource_int_value("apic", 0, "clock", &i) != 0 || i != 0)) {
+ arat = 0;
+ /* Intel CPUID 0x06 EAX[2] set if APIC timer runs in C3. */
+ if (cpu_vendor_id == CPU_VENDOR_INTEL && cpu_high >= 6) {
+ do_cpuid(0x06, regs);
+ if (regs[0] & 0x4)
+ arat = 1;
+ }
+ bzero(&lapic_et, sizeof(lapic_et));
+ lapic_et.et_name = "LAPIC";
+ lapic_et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT |
+ ET_FLAGS_PERCPU;
+ lapic_et.et_quality = 600;
+ if (!arat) {
+ lapic_et.et_flags |= ET_FLAGS_C3STOP;
+ lapic_et.et_quality -= 100;
+ }
+ lapic_et.et_frequency = 0;
+ lapic_et.et_start = lapic_et_start;
+ lapic_et.et_stop = lapic_et_stop;
+ lapic_et.et_priv = NULL;
+ et_register(&lapic_et);
+ }
}
/*
@@ -328,16 +356,19 @@ lapic_setup(int boot)
/* Program timer LVT and setup handler. */
lapic->lvt_timer = lvt_mode(la, LVT_TIMER, lapic->lvt_timer);
if (boot) {
- snprintf(buf, sizeof(buf), "cpu%d: timer", PCPU_GET(cpuid));
+ snprintf(buf, sizeof(buf), "cpu%d:timer", PCPU_GET(cpuid));
intrcnt_add(buf, &la->la_timer_count);
}
- /* We don't setup the timer during boot on the BSP until later. */
- if (!(boot && PCPU_GET(cpuid) == 0) && lapic_timer_hz != 0) {
- KASSERT(lapic_timer_period != 0, ("lapic%u: zero divisor",
+ /* Setup the timer if configured. */
+ if (la->la_timer_mode != 0) {
+ KASSERT(la->la_timer_period != 0, ("lapic%u: zero divisor",
lapic_id()));
lapic_timer_set_divisor(lapic_timer_divisor);
- lapic_timer_periodic(lapic_timer_period);
+ if (la->la_timer_mode == 1)
+ lapic_timer_periodic(la->la_timer_period);
+ else
+ lapic_timer_oneshot(la->la_timer_period);
lapic_timer_enable_intr();
}
@@ -436,88 +467,66 @@ lapic_disable_pmc(void)
#endif
}
-/*
- * Called by cpu_initclocks() on the BSP to setup the local APIC timer so
- * that it can drive hardclock, statclock, and profclock.
- */
-enum lapic_clock
-lapic_setup_clock(enum lapic_clock srcsdes)
+static int
+lapic_et_start(struct eventtimer *et,
+ struct bintime *first, struct bintime *period)
{
+ struct lapic *la;
u_long value;
- int i;
-
- /* lapic_setup_clock() should not be called with LAPIC_CLOCK_NONE. */
- MPASS(srcsdes != LAPIC_CLOCK_NONE);
-
- /* Can't drive the timer without a local APIC. */
- if (lapic == NULL ||
- (resource_int_value("apic", 0, "clock", &i) == 0 && i == 0)) {
- clockcoverage = LAPIC_CLOCK_NONE;
- return (clockcoverage);
- }
- /* Start off with a divisor of 2 (power on reset default). */
- lapic_timer_divisor = 2;
-
- /* Try to calibrate the local APIC timer. */
- do {
- lapic_timer_set_divisor(lapic_timer_divisor);
- lapic_timer_oneshot(APIC_TIMER_MAX_COUNT);
- DELAY(2000000);
- value = APIC_TIMER_MAX_COUNT - lapic->ccr_timer;
- if (value != APIC_TIMER_MAX_COUNT)
- break;
- lapic_timer_divisor <<= 1;
- } while (lapic_timer_divisor <= 128);
- if (lapic_timer_divisor > 128)
- panic("lapic: Divisor too big");
- value /= 2;
- if (bootverbose)
- printf("lapic: Divisor %lu, Frequency %lu Hz\n",
- lapic_timer_divisor, value);
-
- /*
- * We want to run stathz in the neighborhood of 128hz. We would
- * like profhz to run as often as possible, so we let it run on
- * each clock tick. We try to honor the requested 'hz' value as
- * much as possible.
- *
- * If 'hz' is above 1500, then we just let the lapic timer
- * (and profhz) run at hz. If 'hz' is below 1500 but above
- * 750, then we let the lapic timer run at 2 * 'hz'. If 'hz'
- * is below 750 then we let the lapic timer run at 4 * 'hz'.
- *
- * Please note that stathz and profhz are set only if all the
- * clocks are handled through the local APIC.
- */
- if (srcsdes == LAPIC_CLOCK_ALL) {
- if (hz >= 1500)
- lapic_timer_hz = hz;
- else if (hz >= 750)
- lapic_timer_hz = hz * 2;
- else
- lapic_timer_hz = hz * 4;
- } else
- lapic_timer_hz = hz;
- lapic_timer_period = value / lapic_timer_hz;
- timer1hz = lapic_timer_hz;
- if (srcsdes == LAPIC_CLOCK_ALL) {
- if (lapic_timer_hz < 128)
- stathz = lapic_timer_hz;
- else
- stathz = lapic_timer_hz / (lapic_timer_hz / 128);
- profhz = lapic_timer_hz;
- timer2hz = 0;
+ if (et->et_frequency == 0) {
+ /* Start off with a divisor of 2 (power on reset default). */
+ lapic_timer_divisor = 2;
+ /* Try to calibrate the local APIC timer. */
+ do {
+ lapic_timer_set_divisor(lapic_timer_divisor);
+ lapic_timer_oneshot(APIC_TIMER_MAX_COUNT);
+ DELAY(1000000);
+ value = APIC_TIMER_MAX_COUNT - lapic->ccr_timer;
+ if (value != APIC_TIMER_MAX_COUNT)
+ break;
+ lapic_timer_divisor <<= 1;
+ } while (lapic_timer_divisor <= 128);
+ if (lapic_timer_divisor > 128)
+ panic("lapic: Divisor too big");
+ if (bootverbose)
+ printf("lapic: Divisor %lu, Frequency %lu Hz\n",
+ lapic_timer_divisor, value);
+ et->et_frequency = value;
}
-
+ la = &lapics[lapic_id()];
/*
* Start up the timer on the BSP. The APs will kick off their
* timer during lapic_setup().
*/
- lapic_timer_periodic(lapic_timer_period);
+ lapic_timer_set_divisor(lapic_timer_divisor);
+ if (period != NULL) {
+ la->la_timer_mode = 1;
+ la->la_timer_period =
+ (et->et_frequency * (period->frac >> 32)) >> 32;
+ if (period->sec != 0)
+ la->la_timer_period += et->et_frequency * period->sec;
+ lapic_timer_periodic(la->la_timer_period);
+ } else {
+ la->la_timer_mode = 2;
+ la->la_timer_period =
+ (et->et_frequency * (first->frac >> 32)) >> 32;
+ if (first->sec != 0)
+ la->la_timer_period += et->et_frequency * first->sec;
+ lapic_timer_oneshot(la->la_timer_period);
+ }
lapic_timer_enable_intr();
- clockcoverage = srcsdes;
- return (srcsdes);
+ return (0);
+}
+
+static int
+lapic_et_stop(struct eventtimer *et)
+{
+ struct lapic *la = &lapics[lapic_id()];
+
+ la->la_timer_mode = 0;
+ lapic_timer_stop();
+ return (0);
}
void
@@ -763,6 +772,8 @@ void
lapic_handle_timer(struct trapframe *frame)
{
struct lapic *la;
+ struct trapframe *oldframe;
+ struct thread *td;
/* Send EOI first thing. */
lapic_eoi();
@@ -787,19 +798,14 @@ lapic_handle_timer(struct trapframe *frame)
la = &lapics[PCPU_GET(apic_id)];
(*la->la_timer_count)++;
critical_enter();
-
-#ifdef KDTRACE_HOOKS
- /*
- * If the DTrace hooks are configured and a callback function
- * has been registered, then call it to process the high speed
- * timers.
- */
- int cpu = PCPU_GET(cpuid);
- if (cyclic_clock_func[cpu] != NULL)
- (*cyclic_clock_func[cpu])(frame);
-#endif
-
- timer1clock(TRAPF_USERMODE(frame), TRAPF_PC(frame));
+ if (lapic_et.et_active) {
+ td = curthread;
+ oldframe = td->td_intr_frame;
+ td->td_intr_frame = frame;
+ lapic_et.et_event_cb(&lapic_et,
+ lapic_et.et_arg ? lapic_et.et_arg : frame);
+ td->td_intr_frame = oldframe;
+ }
critical_exit();
}
@@ -838,6 +844,17 @@ lapic_timer_periodic(u_int count)
}
static void
+lapic_timer_stop(void)
+{
+ u_int32_t value;
+
+ value = lapic->lvt_timer;
+ value &= ~APIC_LVTT_TM;
+ value &= ~APIC_LVT_M;
+ lapic->lvt_timer = value;
+}
+
+static void
lapic_timer_enable_intr(void)
{
u_int32_t value;
diff --git a/sys/x86/x86/timeevents.c b/sys/x86/x86/timeevents.c
new file mode 100644
index 0000000..d31af5d
--- /dev/null
+++ b/sys/x86/x86/timeevents.c
@@ -0,0 +1,509 @@
+/*-
+ * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org>
+ * 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,
+ * without modification, immediately at the beginning of the file.
+ * 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 ``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 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Common routines to manage event timers hardware.
+ */
+
+/* XEN has own timer routines now. */
+#ifndef XEN
+
+#include "opt_clock.h"
+#include "opt_kdtrace.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/kdb.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/kernel.h>
+#include <sys/sched.h>
+#include <sys/smp.h>
+#include <sys/sysctl.h>
+#include <sys/timeet.h>
+
+#include <machine/atomic.h>
+#include <machine/clock.h>
+#include <machine/cpu.h>
+#include <machine/intr_machdep.h>
+#include <machine/md_var.h>
+#include <machine/smp.h>
+
+#ifdef KDTRACE_HOOKS
+#include <sys/dtrace_bsd.h>
+cyclic_clock_func_t cyclic_clock_func[MAXCPU];
+#endif
+
+static void cpu_restartclocks(void);
+static void timercheck(void);
+inline static int doconfigtimer(int i);
+static void configtimer(int i);
+
+static struct eventtimer *timer[2] = { NULL, NULL };
+static int timertest = 0;
+static int timerticks[2] = { 0, 0 };
+static int profiling_on = 0;
+static struct bintime timerperiod[2];
+
+static char timername[2][32];
+TUNABLE_STR("kern.eventtimer.timer1", timername[0], sizeof(*timername));
+TUNABLE_STR("kern.eventtimer.timer2", timername[1], sizeof(*timername));
+
+static u_int singlemul = 0;
+TUNABLE_INT("kern.eventtimer.singlemul", &singlemul);
+SYSCTL_INT(_kern_eventtimer, OID_AUTO, singlemul, CTLFLAG_RW, &singlemul,
+ 0, "Multiplier, used in single timer mode");
+
+typedef u_int tc[2];
+static DPCPU_DEFINE(tc, configtimer);
+
+#define FREQ2BT(freq, bt) \
+{ \
+ (bt)->sec = 0; \
+ (bt)->frac = ((uint64_t)0x8000000000000000 / (freq)) << 1; \
+}
+#define BT2FREQ(bt, freq) \
+{ \
+ *(freq) = ((uint64_t)0x8000000000000000 + ((bt)->frac >> 2)) / \
+ ((bt)->frac >> 1); \
+}
+
+/* Per-CPU timer1 handler. */
+static int
+hardclockhandler(struct trapframe *frame)
+{
+
+#ifdef KDTRACE_HOOKS
+ /*
+ * If the DTrace hooks are configured and a callback function
+ * has been registered, then call it to process the high speed
+ * timers.
+ */
+ int cpu = curcpu;
+ if (cyclic_clock_func[cpu] != NULL)
+ (*cyclic_clock_func[cpu])(frame);
+#endif
+
+ timer1clock(TRAPF_USERMODE(frame), TRAPF_PC(frame));
+ return (FILTER_HANDLED);
+}
+
+/* Per-CPU timer2 handler. */
+static int
+statclockhandler(struct trapframe *frame)
+{
+
+ timer2clock(TRAPF_USERMODE(frame), TRAPF_PC(frame));
+ return (FILTER_HANDLED);
+}
+
+/* timer1 broadcast IPI handler. */
+int
+hardclockintr(struct trapframe *frame)
+{
+
+ if (doconfigtimer(0))
+ return (FILTER_HANDLED);
+ return (hardclockhandler(frame));
+}
+
+/* timer2 broadcast IPI handler. */
+int
+statclockintr(struct trapframe *frame)
+{
+
+ if (doconfigtimer(1))
+ return (FILTER_HANDLED);
+ return (statclockhandler(frame));
+}
+
+/* timer1 callback. */
+static void
+timer1cb(struct eventtimer *et, void *arg)
+{
+
+#ifdef SMP
+ /* Broadcast interrupt to other CPUs for non-per-CPU timers */
+ if (smp_started && (et->et_flags & ET_FLAGS_PERCPU) == 0)
+ ipi_all_but_self(IPI_HARDCLOCK);
+#endif
+ if (timertest) {
+ if ((et->et_flags & ET_FLAGS_PERCPU) == 0 || curcpu == 0) {
+ timerticks[0]++;
+ if (timerticks[0] >= timer1hz) {
+ ET_LOCK();
+ timercheck();
+ ET_UNLOCK();
+ }
+ }
+ }
+ hardclockhandler((struct trapframe *)arg);
+}
+
+/* timer2 callback. */
+static void
+timer2cb(struct eventtimer *et, void *arg)
+{
+
+#ifdef SMP
+ /* Broadcast interrupt to other CPUs for non-per-CPU timers */
+ if (smp_started && (et->et_flags & ET_FLAGS_PERCPU) == 0)
+ ipi_all_but_self(IPI_STATCLOCK);
+#endif
+ if (timertest) {
+ if ((et->et_flags & ET_FLAGS_PERCPU) == 0 || curcpu == 0) {
+ timerticks[1]++;
+ if (timerticks[1] >= timer2hz * 2) {
+ ET_LOCK();
+ timercheck();
+ ET_UNLOCK();
+ }
+ }
+ }
+ statclockhandler((struct trapframe *)arg);
+}
+
+/*
+ * Check that both timers are running with at least 1/4 of configured rate.
+ * If not - replace the broken one.
+ */
+static void
+timercheck(void)
+{
+
+ if (!timertest)
+ return;
+ timertest = 0;
+ if (timerticks[0] * 4 < timer1hz) {
+ printf("Event timer \"%s\" is dead.\n", timer[0]->et_name);
+ timer1hz = 0;
+ configtimer(0);
+ et_ban(timer[0]);
+ et_free(timer[0]);
+ timer[0] = et_find(NULL, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ if (timer[0] == NULL) {
+ timer2hz = 0;
+ configtimer(1);
+ et_free(timer[1]);
+ timer[1] = NULL;
+ timer[0] = timer[1];
+ }
+ et_init(timer[0], timer1cb, NULL, NULL);
+ cpu_restartclocks();
+ return;
+ }
+ if (timerticks[1] * 4 < timer2hz) {
+ printf("Event timer \"%s\" is dead.\n", timer[1]->et_name);
+ timer2hz = 0;
+ configtimer(1);
+ et_ban(timer[1]);
+ et_free(timer[1]);
+ timer[1] = et_find(NULL, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ if (timer[1] != NULL)
+ et_init(timer[1], timer2cb, NULL, NULL);
+ cpu_restartclocks();
+ return;
+ }
+}
+
+/*
+ * Reconfigure specified per-CPU timer on other CPU. Called from IPI handler.
+ */
+inline static int
+doconfigtimer(int i)
+{
+ tc *conf;
+
+ conf = DPCPU_PTR(configtimer);
+ if (atomic_load_acq_int(*conf + i)) {
+ if (i == 0 ? timer1hz : timer2hz)
+ et_start(timer[i], NULL, &timerperiod[i]);
+ else
+ et_stop(timer[i]);
+ atomic_store_rel_int(*conf + i, 0);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Reconfigure specified timer.
+ * For per-CPU timers use IPI to make other CPUs to reconfigure.
+ */
+static void
+configtimer(int i)
+{
+#ifdef SMP
+ tc *conf;
+ int cpu;
+
+ critical_enter();
+#endif
+ /* Start/stop global timer or per-CPU timer of this CPU. */
+ if (i == 0 ? timer1hz : timer2hz)
+ et_start(timer[i], NULL, &timerperiod[i]);
+ else
+ et_stop(timer[i]);
+#ifdef SMP
+ if ((timer[i]->et_flags & ET_FLAGS_PERCPU) == 0 || !smp_started) {
+ critical_exit();
+ return;
+ }
+ /* Set reconfigure flags for other CPUs. */
+ CPU_FOREACH(cpu) {
+ conf = DPCPU_ID_PTR(cpu, configtimer);
+ atomic_store_rel_int(*conf + i, (cpu == curcpu) ? 0 : 1);
+ }
+ /* Send reconfigure IPI. */
+ ipi_all_but_self(i == 0 ? IPI_HARDCLOCK : IPI_STATCLOCK);
+ /* Wait for reconfiguration completed. */
+restart:
+ cpu_spinwait();
+ CPU_FOREACH(cpu) {
+ if (cpu == curcpu)
+ continue;
+ conf = DPCPU_ID_PTR(cpu, configtimer);
+ if (atomic_load_acq_int(*conf + i))
+ goto restart;
+ }
+ critical_exit();
+#endif
+}
+
+/*
+ * Configure and start event timers.
+ */
+void
+cpu_initclocks_bsp(void)
+{
+ int base, div;
+
+ timer[0] = et_find(timername[0], ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ if (timer[0] == NULL)
+ timer[0] = et_find(NULL, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ et_init(timer[0], timer1cb, NULL, NULL);
+ timer[1] = et_find(timername[1][0] ? timername[1] : NULL,
+ ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ if (timer[1])
+ et_init(timer[1], timer2cb, NULL, NULL);
+ /*
+ * We honor the requested 'hz' value.
+ * We want to run stathz in the neighborhood of 128hz.
+ * We would like profhz to run as often as possible.
+ */
+ if (singlemul == 0) {
+ if (hz >= 1500 || (hz % 128) == 0)
+ singlemul = 1;
+ else if (hz >= 750)
+ singlemul = 2;
+ else
+ singlemul = 4;
+ }
+ if (timer[1] == NULL) {
+ base = hz * singlemul;
+ if (base < 128)
+ stathz = base;
+ else {
+ div = base / 128;
+ if (div % 2 == 0)
+ div++;
+ stathz = base / div;
+ }
+ profhz = stathz;
+ while ((profhz + stathz) <= 8192)
+ profhz += stathz;
+ } else {
+ stathz = 128;
+ profhz = stathz * 64;
+ }
+ ET_LOCK();
+ cpu_restartclocks();
+ ET_UNLOCK();
+}
+
+/* Start per-CPU event timers on APs. */
+void
+cpu_initclocks_ap(void)
+{
+
+ ET_LOCK();
+ if (timer[0]->et_flags & ET_FLAGS_PERCPU)
+ et_start(timer[0], NULL, &timerperiod[0]);
+ if (timer[1] && timer[1]->et_flags & ET_FLAGS_PERCPU)
+ et_start(timer[1], NULL, &timerperiod[1]);
+ ET_UNLOCK();
+}
+
+/* Reconfigure and restart event timers after configuration changes. */
+static void
+cpu_restartclocks(void)
+{
+
+ /* Stop all event timers. */
+ timertest = 0;
+ if (timer1hz) {
+ timer1hz = 0;
+ configtimer(0);
+ }
+ if (timer[1] && timer2hz) {
+ timer2hz = 0;
+ configtimer(1);
+ }
+ /* Calculate new event timers parameters. */
+ if (timer[1] == NULL) {
+ timer1hz = hz * singlemul;
+ while (timer1hz < (profiling_on ? profhz : stathz))
+ timer1hz += hz;
+ timer2hz = 0;
+ } else {
+ timer1hz = hz;
+ timer2hz = profiling_on ? profhz : stathz;
+ }
+ printf("Starting kernel event timers: %s @ %dHz, %s @ %dHz\n",
+ timer[0]->et_name, timer1hz,
+ timer[1] ? timer[1]->et_name : "NONE", timer2hz);
+ /* Restart event timers. */
+ FREQ2BT(timer1hz, &timerperiod[0]);
+ configtimer(0);
+ if (timer[1]) {
+ timerticks[0] = 0;
+ timerticks[1] = 0;
+ FREQ2BT(timer2hz, &timerperiod[1]);
+ configtimer(1);
+ timertest = 1;
+ }
+}
+
+/* Switch to profiling clock rates. */
+void
+cpu_startprofclock(void)
+{
+
+ ET_LOCK();
+ profiling_on = 1;
+ cpu_restartclocks();
+ ET_UNLOCK();
+}
+
+/* Switch to regular clock rates. */
+void
+cpu_stopprofclock(void)
+{
+
+ ET_LOCK();
+ profiling_on = 0;
+ cpu_restartclocks();
+ ET_UNLOCK();
+}
+
+/* Report or change the active event timers hardware. */
+static int
+sysctl_kern_eventtimer_timer1(SYSCTL_HANDLER_ARGS)
+{
+ char buf[32];
+ struct eventtimer *et;
+ int error;
+
+ ET_LOCK();
+ et = timer[0];
+ snprintf(buf, sizeof(buf), "%s", et->et_name);
+ ET_UNLOCK();
+ error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
+ ET_LOCK();
+ et = timer[0];
+ if (error != 0 || req->newptr == NULL ||
+ strcmp(buf, et->et_name) == 0) {
+ ET_UNLOCK();
+ return (error);
+ }
+ et = et_find(buf, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ if (et == NULL) {
+ ET_UNLOCK();
+ return (ENOENT);
+ }
+ timer1hz = 0;
+ configtimer(0);
+ et_free(timer[0]);
+ timer[0] = et;
+ et_init(timer[0], timer1cb, NULL, NULL);
+ cpu_restartclocks();
+ ET_UNLOCK();
+ return (error);
+}
+SYSCTL_PROC(_kern_eventtimer, OID_AUTO, timer1,
+ CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, 0, sysctl_kern_eventtimer_timer1, "A", "Primary event timer");
+
+static int
+sysctl_kern_eventtimer_timer2(SYSCTL_HANDLER_ARGS)
+{
+ char buf[32];
+ struct eventtimer *et;
+ int error;
+
+ ET_LOCK();
+ et = timer[1];
+ if (et == NULL)
+ snprintf(buf, sizeof(buf), "NONE");
+ else
+ snprintf(buf, sizeof(buf), "%s", et->et_name);
+ ET_UNLOCK();
+ error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
+ ET_LOCK();
+ et = timer[1];
+ if (error != 0 || req->newptr == NULL ||
+ strcmp(buf, et ? et->et_name : "NONE") == 0) {
+ ET_UNLOCK();
+ return (error);
+ }
+ et = et_find(buf, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC);
+ if (et == NULL && strcasecmp(buf, "NONE") != 0) {
+ ET_UNLOCK();
+ return (ENOENT);
+ }
+ if (timer[1] != NULL) {
+ timer2hz = 0;
+ configtimer(1);
+ et_free(timer[1]);
+ }
+ timer[1] = et;
+ if (timer[1] != NULL)
+ et_init(timer[1], timer2cb, NULL, NULL);
+ cpu_restartclocks();
+ ET_UNLOCK();
+ return (error);
+}
+SYSCTL_PROC(_kern_eventtimer, OID_AUTO, timer2,
+ CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, 0, sysctl_kern_eventtimer_timer2, "A", "Secondary event timer");
+
+#endif
+
OpenPOWER on IntegriCloud