ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / arch / ppc / kernel / smp-tbsync.c
1 /*
2  * Smp timebase synchronization for ppc.
3  *
4  * Copyright (C) 2003 Samuel Rydh (samuel@ibrium.se)
5  *
6  */
7
8 #include <linux/config.h>
9 #include <linux/kernel.h>
10 #include <linux/sched.h>
11 #include <linux/smp.h>
12 #include <linux/unistd.h>
13 #include <linux/init.h>
14 #include <asm/atomic.h>
15 #include <asm/smp.h>
16 #include <asm/time.h>
17
18 #define NUM_ITER                300
19
20 enum {
21         kExit=0, kSetAndTest, kTest
22 };
23
24 static struct {
25         volatile int            tbu;
26         volatile int            tbl;
27         volatile int            mark;
28         volatile int            cmd;
29         volatile int            handshake;
30         int                     filler[3];
31
32         volatile int            ack;
33         int                     filler2[7];
34
35         volatile int            race_result;
36 } *tbsync;
37
38 static volatile int             running;
39
40 static void __devinit
41 enter_contest( int mark, int add )
42 {
43         while( (int)(get_tbl() - mark) < 0 )
44                 tbsync->race_result = add;
45 }
46
47 void __devinit
48 smp_generic_take_timebase( void )
49 {
50         int cmd, tbl, tbu;
51
52         local_irq_disable();
53         while( !running )
54                 ;
55         rmb();
56
57         for( ;; ) {
58                 tbsync->ack = 1;
59                 while( !tbsync->handshake )
60                         ;
61                 rmb();
62
63                 cmd = tbsync->cmd;
64                 tbl = tbsync->tbl;
65                 tbu = tbsync->tbu;
66                 tbsync->ack = 0;
67                 if( cmd == kExit )
68                         return;
69
70                 if( cmd == kSetAndTest ) {
71                         while( tbsync->handshake )
72                                 ;
73                         asm volatile ("mttbl %0" :: "r" (tbl) );
74                         asm volatile ("mttbu %0" :: "r" (tbu) );
75                 } else {
76                         while( tbsync->handshake )
77                                 ;
78                 }
79                 enter_contest( tbsync->mark, -1 );
80         }
81         local_irq_enable();
82 }
83
84 static int __devinit
85 start_contest( int cmd, int offset, int num )
86 {
87         int i, tbu, tbl, mark, score=0;
88
89         tbsync->cmd = cmd;
90
91         local_irq_disable();
92         for( i=-3; i<num; ) {
93                 tbl = get_tbl() + 400;
94                 tbsync->tbu = tbu = get_tbu();
95                 tbsync->tbl = tbl + offset;
96                 tbsync->mark = mark = tbl + 400;
97
98                 wmb();
99
100                 tbsync->handshake = 1;
101                 while( tbsync->ack )
102                         ;
103
104                 while( (int)(get_tbl() - tbl) <= 0 )
105                         ;
106                 tbsync->handshake = 0;
107                 enter_contest( mark, 1 );
108
109                 while( !tbsync->ack )
110                         ;
111
112                 if( tbsync->tbu != get_tbu() || ((tbsync->tbl ^ get_tbl()) & 0x80000000) )
113                         continue;
114                 if( i++ > 0 )
115                         score += tbsync->race_result;
116         }
117         local_irq_enable();
118         return score;
119 }
120
121 void __devinit
122 smp_generic_give_timebase( void )
123 {
124         int i, score, score2, old, min=0, max=5000, offset=1000;
125
126         printk("Synchronizing timebase\n");
127
128         /* if this fails then this kernel won't work anyway... */
129         tbsync = kmalloc( sizeof(*tbsync), GFP_KERNEL );
130         memset( tbsync, 0, sizeof(*tbsync) );
131         mb();
132         running = 1;
133
134         while( !tbsync->ack )
135                 ;
136
137         /* binary search */
138         for( old=-1 ; old != offset ; offset=(min+max)/2 ) {
139                 score = start_contest( kSetAndTest, offset, NUM_ITER );
140
141                 printk("score %d, offset %d\n", score, offset );
142
143                 if( score > 0 )
144                         max = offset;
145                 else
146                         min = offset;
147                 old = offset;
148         }
149         score = start_contest( kSetAndTest, min, NUM_ITER );
150         score2 = start_contest( kSetAndTest, max, NUM_ITER );
151
152         printk( "Min %d (score %d), Max %d (score %d)\n", min, score, max, score2 );
153         score = abs( score );
154         score2 = abs( score2 );
155         offset = (score < score2) ? min : max;
156
157         /* guard against inaccurate mttb */
158         for( i=0; i<10; i++ ) {
159                 start_contest( kSetAndTest, offset, NUM_ITER/10 );
160
161                 if( (score2=start_contest(kTest, offset, NUM_ITER)) < 0 )
162                         score2 = -score2;
163                 if( score2 <= score || score2 < 20 )
164                         break;
165         }
166         printk("Final offset: %d (%d/%d)\n", offset, score2, NUM_ITER );
167
168         /* exiting */
169         tbsync->cmd = kExit;
170         wmb();
171         tbsync->handshake = 1;
172         while( tbsync->ack )
173                 ;
174         tbsync->handshake = 0;
175         kfree( tbsync );
176         tbsync = NULL;
177         running = 0;
178
179         /* all done */
180         smp_tb_synchronized = 1;
181 }