/*- * Copyright (c) 2010 Alexander Motin * 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 __FBSDID("$FreeBSD: head/sys/kern/kern_clocksource.c 210298 2010-07-20 15:48:29Z mav $"); /* * Common routines to manage event timers hardware. */ /* XEN has own timer routines now. */ #ifndef XEN #include "opt_kdtrace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KDTRACE_HOOKS #include 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) \ (((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(curthread->td_intr_frame); } /* 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(curthread->td_intr_frame); } /* * 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 } static int round_freq(struct eventtimer *et, int freq) { uint64_t div; if (et->et_frequency != 0) { div = lmax((et->et_frequency + freq / 2) / freq, 1); if (et->et_flags & ET_FLAGS_POW2DIV) div = 1 << (flsl(div + div / 2) - 1); freq = (et->et_frequency + div / 2) / div; } if (et->et_min_period.sec > 0) freq = 0; else if (et->et_min_period.frac != 0) freq = min(freq, BT2FREQ(&et->et_min_period)); if (et->et_max_period.sec == 0 && et->et_max_period.frac != 0) freq = max(freq, BT2FREQ(&et->et_max_period)); return (freq); } /* * 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); if (timer[0] == NULL) panic("No usable event timer found!"); 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 = round_freq(timer[0], hz * singlemul); singlemul = max((base + hz / 2) / hz, 1); hz = (base + singlemul / 2) / singlemul; if (base <= 128) stathz = base; else { div = base / 128; if (div >= singlemul && (div % singlemul) == 0) div++; stathz = base / div; } profhz = stathz; while ((profhz + stathz) <= 128 * 64) profhz += stathz; profhz = round_freq(timer[0], profhz); } else { hz = round_freq(timer[0], hz); stathz = round_freq(timer[1], 127); profhz = round_freq(timer[1], stathz * 64); } tick = 1000000 / hz; 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; timer2hz = round_freq(timer[1], timer2hz); } timer1hz = round_freq(timer[0], timer1hz); 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