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