linux/arch/powerpc/kernel/smp-tbsync.c
Benjamin Herrenschmidt a6a8e009b1 powerpc: Silence software timebase sync
When no hardware method is provided to sync the timebase registers
across the machine, and the platform doesn't sync them for us, then we
use a generic software implementation.  Currently, the code for that
has many printks, and they don't have log levels.  Most of the printks
are only useful for debugging the code, and since we haven't had any
problems with it for years, this turns them into pr_debug.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
2008-11-05 22:08:28 +11:00

171 lines
3.1 KiB
C

/*
* Smp timebase synchronization for ppc.
*
* Copyright (C) 2003 Samuel Rydh (samuel@ibrium.se)
*
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/unistd.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <asm/smp.h>
#include <asm/time.h>
#define NUM_ITER 300
enum {
kExit=0, kSetAndTest, kTest
};
static struct {
volatile u64 tb;
volatile u64 mark;
volatile int cmd;
volatile int handshake;
int filler[2];
volatile int ack;
int filler2[7];
volatile int race_result;
} *tbsync;
static volatile int running;
static void __devinit enter_contest(u64 mark, long add)
{
while (get_tb() < mark)
tbsync->race_result = add;
}
void __devinit smp_generic_take_timebase(void)
{
int cmd;
u64 tb;
unsigned long flags;
local_irq_save(flags);
while (!running)
barrier();
rmb();
for (;;) {
tbsync->ack = 1;
while (!tbsync->handshake)
barrier();
rmb();
cmd = tbsync->cmd;
tb = tbsync->tb;
mb();
tbsync->ack = 0;
if (cmd == kExit)
break;
while (tbsync->handshake)
barrier();
if (cmd == kSetAndTest)
set_tb(tb >> 32, tb & 0xfffffffful);
enter_contest(tbsync->mark, -1);
}
local_irq_restore(flags);
}
static int __devinit start_contest(int cmd, long offset, int num)
{
int i, score=0;
u64 tb;
u64 mark;
tbsync->cmd = cmd;
local_irq_disable();
for (i = -3; i < num; ) {
tb = get_tb() + 400;
tbsync->tb = tb + offset;
tbsync->mark = mark = tb + 400;
wmb();
tbsync->handshake = 1;
while (tbsync->ack)
barrier();
while (get_tb() <= tb)
barrier();
tbsync->handshake = 0;
enter_contest(mark, 1);
while (!tbsync->ack)
barrier();
if (i++ > 0)
score += tbsync->race_result;
}
local_irq_enable();
return score;
}
void __devinit smp_generic_give_timebase(void)
{
int i, score, score2, old, min=0, max=5000, offset=1000;
pr_debug("Software timebase sync\n");
/* if this fails then this kernel won't work anyway... */
tbsync = kzalloc( sizeof(*tbsync), GFP_KERNEL );
mb();
running = 1;
while (!tbsync->ack)
barrier();
pr_debug("Got ack\n");
/* binary search */
for (old = -1; old != offset ; offset = (min+max) / 2) {
score = start_contest(kSetAndTest, offset, NUM_ITER);
pr_debug("score %d, offset %d\n", score, offset );
if( score > 0 )
max = offset;
else
min = offset;
old = offset;
}
score = start_contest(kSetAndTest, min, NUM_ITER);
score2 = start_contest(kSetAndTest, max, NUM_ITER);
pr_debug("Min %d (score %d), Max %d (score %d)\n",
min, score, max, score2);
score = abs(score);
score2 = abs(score2);
offset = (score < score2) ? min : max;
/* guard against inaccurate mttb */
for (i = 0; i < 10; i++) {
start_contest(kSetAndTest, offset, NUM_ITER/10);
if ((score2 = start_contest(kTest, offset, NUM_ITER)) < 0)
score2 = -score2;
if (score2 <= score || score2 < 20)
break;
}
pr_debug("Final offset: %d (%d/%d)\n", offset, score2, NUM_ITER );
/* exiting */
tbsync->cmd = kExit;
wmb();
tbsync->handshake = 1;
while (tbsync->ack)
barrier();
tbsync->handshake = 0;
kfree(tbsync);
tbsync = NULL;
running = 0;
}