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