2005-04-17 02:20:36 +04:00
/*
* 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 {
2005-11-04 05:28:58 +03:00
volatile u64 tb ;
volatile u64 mark ;
2005-04-17 02:20:36 +04:00
volatile int cmd ;
volatile int handshake ;
2005-11-04 05:28:58 +03:00
int filler [ 2 ] ;
2005-04-17 02:20:36 +04:00
volatile int ack ;
int filler2 [ 7 ] ;
volatile int race_result ;
} * tbsync ;
static volatile int running ;
2005-11-04 05:28:58 +03:00
static void __devinit enter_contest ( u64 mark , long add )
2005-04-17 02:20:36 +04:00
{
2005-11-04 05:28:58 +03:00
while ( get_tb ( ) < mark )
2005-04-17 02:20:36 +04:00
tbsync - > race_result = add ;
}
2005-11-04 05:28:58 +03:00
void __devinit smp_generic_take_timebase ( void )
2005-04-17 02:20:36 +04:00
{
int cmd ;
2005-11-04 05:28:58 +03:00
u64 tb ;
2005-04-17 02:20:36 +04:00
local_irq_disable ( ) ;
2005-11-04 05:28:58 +03:00
while ( ! running )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
rmb ( ) ;
2005-11-04 05:28:58 +03:00
for ( ; ; ) {
2005-04-17 02:20:36 +04:00
tbsync - > ack = 1 ;
2005-11-04 05:28:58 +03:00
while ( ! tbsync - > handshake )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
rmb ( ) ;
cmd = tbsync - > cmd ;
tb = tbsync - > tb ;
2005-11-04 05:28:58 +03:00
mb ( ) ;
2005-04-17 02:20:36 +04:00
tbsync - > ack = 0 ;
2005-11-04 05:28:58 +03:00
if ( cmd = = kExit )
break ;
while ( tbsync - > handshake )
barrier ( ) ;
if ( cmd = = kSetAndTest )
set_tb ( tb > > 32 , tb & 0xfffffffful ) ;
enter_contest ( tbsync - > mark , - 1 ) ;
2005-04-17 02:20:36 +04:00
}
local_irq_enable ( ) ;
}
2005-11-04 05:28:58 +03:00
static int __devinit start_contest ( int cmd , long offset , int num )
2005-04-17 02:20:36 +04:00
{
int i , score = 0 ;
2005-11-04 05:28:58 +03:00
u64 tb ;
long mark ;
2005-04-17 02:20:36 +04:00
tbsync - > cmd = cmd ;
local_irq_disable ( ) ;
2005-11-04 05:28:58 +03:00
for ( i = - 3 ; i < num ; ) {
tb = get_tb ( ) + 400 ;
2005-04-17 02:20:36 +04:00
tbsync - > tb = tb + offset ;
tbsync - > mark = mark = tb + 400 ;
wmb ( ) ;
tbsync - > handshake = 1 ;
2005-11-04 05:28:58 +03:00
while ( tbsync - > ack )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
2005-11-04 05:28:58 +03:00
while ( get_tb ( ) < = tb )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
tbsync - > handshake = 0 ;
2005-11-04 05:28:58 +03:00
enter_contest ( mark , 1 ) ;
2005-04-17 02:20:36 +04:00
2005-11-04 05:28:58 +03:00
while ( ! tbsync - > ack )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
2005-11-04 05:28:58 +03:00
if ( i + + > 0 )
2005-04-17 02:20:36 +04:00
score + = tbsync - > race_result ;
}
local_irq_enable ( ) ;
return score ;
}
2005-11-04 05:28:58 +03:00
void __devinit smp_generic_give_timebase ( void )
2005-04-17 02:20:36 +04:00
{
int i , score , score2 , old , min = 0 , max = 5000 , offset = 1000 ;
printk ( " Synchronizing timebase \n " ) ;
/* if this fails then this kernel won't work anyway... */
tbsync = kmalloc ( sizeof ( * tbsync ) , GFP_KERNEL ) ;
memset ( tbsync , 0 , sizeof ( * tbsync ) ) ;
mb ( ) ;
running = 1 ;
2005-11-04 05:28:58 +03:00
while ( ! tbsync - > ack )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
printk ( " Got ack \n " ) ;
/* binary search */
2005-11-04 05:28:58 +03:00
for ( old = - 1 ; old ! = offset ; offset = ( min + max ) / 2 ) {
score = start_contest ( kSetAndTest , offset , NUM_ITER ) ;
2005-04-17 02:20:36 +04:00
printk ( " score %d, offset %d \n " , score , offset ) ;
if ( score > 0 )
max = offset ;
else
min = offset ;
old = offset ;
}
2005-11-04 05:28:58 +03:00
score = start_contest ( kSetAndTest , min , NUM_ITER ) ;
score2 = start_contest ( kSetAndTest , max , NUM_ITER ) ;
2005-04-17 02:20:36 +04:00
2005-11-04 05:28:58 +03:00
printk ( " Min %d (score %d), Max %d (score %d) \n " ,
min , score , max , score2 ) ;
score = abs ( score ) ;
score2 = abs ( score2 ) ;
2005-04-17 02:20:36 +04:00
offset = ( score < score2 ) ? min : max ;
/* guard against inaccurate mttb */
2005-11-04 05:28:58 +03:00
for ( i = 0 ; i < 10 ; i + + ) {
start_contest ( kSetAndTest , offset , NUM_ITER / 10 ) ;
2005-04-17 02:20:36 +04:00
2005-11-04 05:28:58 +03:00
if ( ( score2 = start_contest ( kTest , offset , NUM_ITER ) ) < 0 )
2005-04-17 02:20:36 +04:00
score2 = - score2 ;
2005-11-04 05:28:58 +03:00
if ( score2 < = score | | score2 < 20 )
2005-04-17 02:20:36 +04:00
break ;
}
printk ( " Final offset: %d (%d/%d) \n " , offset , score2 , NUM_ITER ) ;
/* exiting */
tbsync - > cmd = kExit ;
wmb ( ) ;
tbsync - > handshake = 1 ;
2005-11-04 05:28:58 +03:00
while ( tbsync - > ack )
barrier ( ) ;
2005-04-17 02:20:36 +04:00
tbsync - > handshake = 0 ;
2005-11-04 05:28:58 +03:00
kfree ( tbsync ) ;
2005-04-17 02:20:36 +04:00
tbsync = NULL ;
running = 0 ;
}