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 u64 tb; 25 volatile u64 mark; 26 volatile int cmd; 27 volatile int handshake; 28 int filler[2]; 29 30 volatile int ack; 31 int filler2[7]; 32 33 volatile int race_result; 34 } *tbsync; 35 36 static volatile int running; 37 38 static void __devinit enter_contest(u64 mark, long add) 39 { 40 while (get_tb() < mark) 41 tbsync->race_result = add; 42 } 43 44 void __devinit smp_generic_take_timebase(void) 45 { 46 int cmd; 47 u64 tb; 48 unsigned long flags; 49 50 local_irq_save(flags); 51 while (!running) 52 barrier(); 53 rmb(); 54 55 for (;;) { 56 tbsync->ack = 1; 57 while (!tbsync->handshake) 58 barrier(); 59 rmb(); 60 61 cmd = tbsync->cmd; 62 tb = tbsync->tb; 63 mb(); 64 tbsync->ack = 0; 65 if (cmd == kExit) 66 break; 67 68 while (tbsync->handshake) 69 barrier(); 70 if (cmd == kSetAndTest) 71 set_tb(tb >> 32, tb & 0xfffffffful); 72 enter_contest(tbsync->mark, -1); 73 } 74 local_irq_restore(flags); 75 } 76 77 static int __devinit start_contest(int cmd, long offset, int num) 78 { 79 int i, score=0; 80 u64 tb; 81 u64 mark; 82 83 tbsync->cmd = cmd; 84 85 local_irq_disable(); 86 for (i = -3; i < num; ) { 87 tb = get_tb() + 400; 88 tbsync->tb = tb + offset; 89 tbsync->mark = mark = tb + 400; 90 91 wmb(); 92 93 tbsync->handshake = 1; 94 while (tbsync->ack) 95 barrier(); 96 97 while (get_tb() <= tb) 98 barrier(); 99 tbsync->handshake = 0; 100 enter_contest(mark, 1); 101 102 while (!tbsync->ack) 103 barrier(); 104 105 if (i++ > 0) 106 score += tbsync->race_result; 107 } 108 local_irq_enable(); 109 return score; 110 } 111 112 void __devinit smp_generic_give_timebase(void) 113 { 114 int i, score, score2, old, min=0, max=5000, offset=1000; 115 116 pr_debug("Software timebase sync\n"); 117 118 /* if this fails then this kernel won't work anyway... */ 119 tbsync = kzalloc( sizeof(*tbsync), GFP_KERNEL ); 120 mb(); 121 running = 1; 122 123 while (!tbsync->ack) 124 barrier(); 125 126 pr_debug("Got ack\n"); 127 128 /* binary search */ 129 for (old = -1; old != offset ; offset = (min+max) / 2) { 130 score = start_contest(kSetAndTest, offset, NUM_ITER); 131 132 pr_debug("score %d, offset %d\n", score, offset ); 133 134 if( score > 0 ) 135 max = offset; 136 else 137 min = offset; 138 old = offset; 139 } 140 score = start_contest(kSetAndTest, min, NUM_ITER); 141 score2 = start_contest(kSetAndTest, max, NUM_ITER); 142 143 pr_debug("Min %d (score %d), Max %d (score %d)\n", 144 min, score, max, score2); 145 score = abs(score); 146 score2 = abs(score2); 147 offset = (score < score2) ? min : max; 148 149 /* guard against inaccurate mttb */ 150 for (i = 0; i < 10; i++) { 151 start_contest(kSetAndTest, offset, NUM_ITER/10); 152 153 if ((score2 = start_contest(kTest, offset, NUM_ITER)) < 0) 154 score2 = -score2; 155 if (score2 <= score || score2 < 20) 156 break; 157 } 158 pr_debug("Final offset: %d (%d/%d)\n", offset, score2, NUM_ITER ); 159 160 /* exiting */ 161 tbsync->cmd = kExit; 162 wmb(); 163 tbsync->handshake = 1; 164 while (tbsync->ack) 165 barrier(); 166 tbsync->handshake = 0; 167 kfree(tbsync); 168 tbsync = NULL; 169 running = 0; 170 } 171