- Add a workaround for a bug in BlackBird CPUs (said to be part of the

SpitFire erratum #54) which can cause writes to the TICK_CMPR register
  to fail. This seems to fix the dying clocks problem reported by jhb@
  and kris@. [1]
- In tick_start() don't reset the tick counter of the boot processor to
  zero. It's initially reset in _start() and afterwards but _before_
  tick_start() is called on the BSP the APs synchronise with the tick
  counter of the BSP in mp_startup(). Resetting the tick counter of the
  BSP in tick_start() probably also was the cause of problems seen when
  using the CPU tick counter as timecounter on SMP machines.
  Not resetting the tick counter of the BSP in mp_startup() makes the
  tick counters and tick interrupts between the BSP and APs be pretty
  much in sync as it's supposed to be. This also means there's no longer
  a real reason to have separate tick_start() and tick_start_ap() so
  merge them and zap tick_start_ap(). This is also a first step in
  simplifying the interface to the tick counters in preparation to use
  alternate clock hardware where available.
- Switch to the algorithm used on FreeBSD/ia64 for updating the tick
  interrupt register and which compensates the clock drift caused by
  varying delays between when the tick interrupts actually trigger and
  when they are serviced. Not compensating the clock drift mainly hurts
  interactive performance especially when using WITNESS. [2]
  For further information about the algorithm also see the commit log
  of sys/ia64/ia64/interrupt.c rev. 1.38.
  On sparc64 the sysctls for monitoring the behaviour of the tick
  interrupts are machdep.tick.adjust_edges, machdep.tick.adjust_excess,
  machdep.tick.adjust_missed and machdep.tick.adjust_ticks.
- In tick_init() just use tick_stop() for stopping the tick interrupts
  until a proper handler is set up later. This also stops the system
  tick interrupt on USIII systems earlier.
- In tick_start() check for a rough upper limit of HZ.
- Some minor changes, e.g. use FBSDID, remove unused headers, etc.

Info obtained from:	Linux [1]
Ok'ed by:		marcel [2]
Additional testing by:	kris (earlier version of the workaround), jhb
X-MFC after:		3 days [1]
This commit is contained in:
Marius Strobl 2005-04-16 14:57:38 +00:00
parent c066bca62d
commit 7bed9b320b
5 changed files with 116 additions and 65 deletions

View File

@ -171,6 +171,23 @@ int fasword32(u_long asi, void *addr, uint32_t *val);
: : "r" (val), "rI" (xor)); \
} while (0)
/*
* Macro intended to be used instead of wr(asr23, val, xor) for writing to
* the TICK_CMPR register in order to avoid a bug in BlackBird CPUs that
* can cause these writes to fail under certain condidtions which in turn
* causes the hardclock to stop. The workaround is to perform the write
* at the beginning of an I-Cache line directly followed by a dummy read.
*/
#define wrtickcmpr(val, xor) ({ \
__asm __volatile( \
" ba,pt %%xcc, 1f ; " \
" nop ; " \
" .align 64 ; " \
"1: wr %0, %1, %%asr23 ; " \
" rd %%asr23, %%g0 ; " \
: : "r" (val), "rI" (xor)); \
})
static __inline void
breakpoint(void)
{

View File

@ -51,6 +51,8 @@ struct pmap;
struct intr_request *pc_irfree; \
struct pmap *pc_pmap; \
vm_offset_t pc_addr; \
u_long pc_tickref; \
u_long pc_tickadj; \
u_int pc_mid; \
u_int pc_node; \
u_int pc_tlb_ctx; \

View File

@ -29,15 +29,8 @@
#ifndef _MACHINE_TICK_H_
#define _MACHINE_TICK_H_
typedef void tick_func_t(struct clockframe *);
void tick_init(u_long clock);
void tick_start(tick_func_t *func);
#ifdef SMP
void tick_start_ap(void);
#endif
void tick_start(void);
void tick_stop(void);
tick_func_t tick_hardclock;
#endif

View File

@ -51,10 +51,11 @@
* 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.
*
* $FreeBSD$
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/lock.h>
@ -342,7 +343,7 @@ cpu_mp_bootstrap(struct pcpu *pc)
csa = &cpu_start_args;
pmap_map_tsb();
cpu_setregs(pc);
tick_start_ap();
tick_start();
smp_cpus++;
KASSERT(curthread != NULL, ("cpu_mp_bootstrap: curthread"));

View File

@ -22,44 +22,54 @@
* 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.
*
* $FreeBSD$
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/interrupt.h>
#include <sys/pcpu.h>
#include <sys/sysctl.h>
#include <sys/timetc.h>
#ifdef SMP
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#endif
#include <dev/ofw/openfirm.h>
#include <machine/clock.h>
#include <machine/frame.h>
#include <machine/intr_machdep.h>
#include <machine/tick.h>
#include <machine/ver.h>
#ifdef SMP
#include <machine/cpu.h>
#endif
int tick_missed; /* statistics */
#define TICK_GRACE 10000
SYSCTL_NODE(_machdep, OID_AUTO, tick, CTLFLAG_RD, 0, "tick statistics");
static int adjust_edges = 0;
SYSCTL_INT(_machdep_tick, OID_AUTO, adjust_edges, CTLFLAG_RD, &adjust_edges,
0, "total number of times tick interrupts got more than 12.5% behind");
static int adjust_excess = 0;
SYSCTL_INT(_machdep_tick, OID_AUTO, adjust_excess, CTLFLAG_RD, &adjust_excess,
0, "total number of ignored tick interrupts");
static int adjust_missed = 0;
SYSCTL_INT(_machdep_tick, OID_AUTO, adjust_missed, CTLFLAG_RD, &adjust_missed,
0, "total number of missed tick interrupts");
static int adjust_ticks = 0;
SYSCTL_INT(_machdep_tick, OID_AUTO, adjust_ticks, CTLFLAG_RD, &adjust_ticks,
0, "total number of tick interrupts with adjustment");
static void tick_hardclock(struct clockframe *);
void
cpu_initclocks(void)
{
stathz = hz;
tick_start(tick_hardclock);
tick_start();
}
static __inline void
@ -75,72 +85,100 @@ tick_process(struct clockframe *cf)
statclock(cf);
}
void
static void
tick_hardclock(struct clockframe *cf)
{
int missed;
u_long next;
register_t i;
u_long adj, s, tick, ref;
long delta;
int count;
tick_process(cf);
/*
* Avoid stopping of hardclock in case we missed one tick period by
* ensuring that the the value of the next tick is at least TICK_GRACE
* ticks in the future.
* Missed ticks need to be accounted for by repeatedly calling
* hardclock.
* The sequence of reading the TICK register, calculating the value
* of the next tick and writing it to the TICK_CMPR register must not
* be interrupted, not even by an IPI, otherwise a value that is in
* the past could be written in the worst case, causing hardclock to
* stop.
*/
missed = 0;
next = rd(asr23) + tick_increment;
i = intr_disable();
while (next < rd(tick) + TICK_GRACE) {
next += tick_increment;
missed++;
}
wr(asr23, next, 0);
intr_restore(i);
atomic_add_int(&tick_missed, missed);
for (; missed > 0; missed--)
adj = PCPU_GET(tickadj);
s = intr_disable();
tick = rd(tick);
wrtickcmpr(tick + tick_increment - adj, 0);
intr_restore(s);
ref = PCPU_GET(tickref);
delta = tick - ref;
count = 0;
while (delta >= tick_increment) {
tick_process(cf);
delta -= tick_increment;
ref += tick_increment;
if (adj != 0)
adjust_ticks++;
count++;
}
if (count > 0) {
adjust_missed += count - 1;
if (delta > (tick_increment >> 3)) {
if (adj == 0)
adjust_edges++;
adj = tick_increment >> 4;
} else
adj = 0;
} else {
adj = 0;
adjust_excess++;
}
PCPU_SET(tickref, ref);
PCPU_SET(tickadj, adj);
}
void
tick_init(u_long clock)
{
tick_freq = clock;
tick_MHz = clock / 1000000;
tick_increment = clock / hz;
/*
* Avoid stopping of hardclock in terms of a lost tick interrupt
* by ensuring that the tick period is at least TICK_GRACE ticks.
*/
if (tick_increment < TICK_GRACE)
panic("%s: HZ to high, decrease to at least %ld", __func__,
clock / TICK_GRACE);
/*
* UltraSparc II[e,i] based systems come up with the tick interrupt
* enabled and a handler that resets the tick counter, causing DELAY()
* to not work properly when used early in boot.
* UltraSPARC III based systems come up with the system tick interrupt
* enabled, causing an interrupt storm on startup since they are not
* handled.
*/
wr(asr23, 1L << 63, 0);
tick_stop();
}
void
tick_start(tick_func_t *func)
tick_start(void)
{
intr_setup(PIL_TICK, (ih_func_t *)func, -1, NULL, NULL);
wrpr(tick, 0, 0);
wr(asr23, tick_increment, 0);
}
u_long base, s;
#ifdef SMP
void
tick_start_ap(void)
{
u_long base;
if (PCPU_GET(cpuid) == 0)
intr_setup(PIL_TICK, (ih_func_t *)tick_hardclock, -1, NULL,
NULL);
/*
* Try to make the ticks interrupt as synchronously as possible to
* avoid inaccuracies for migrating processes. Leave out one tick to
* make sure that it is not missed.
* Try to make the tick interrupts as synchronously as possible on
* all CPUs to avoid inaccuracies for migrating processes. Leave out
* one tick to make sure that it is not missed.
*/
PCPU_SET(tickadj, 0);
s = intr_disable();
base = rd(tick);
wr(asr23, roundup(base, tick_increment) + tick_increment, 0);
base = roundup(base, tick_increment);
PCPU_SET(tickref, base);
wrtickcmpr(base + tick_increment, 0);
intr_restore(s);
}
#endif
void
tick_stop(void)
@ -148,5 +186,5 @@ tick_stop(void)
if (cpu_impl >= CPU_IMPL_ULTRASPARCIII)
wr(asr24, 1L << 63, 0);
wr(asr23, 1L << 63, 0);
wrtickcmpr(1L << 63, 0);
}