Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Debugging versions of SMP locking primitives. | |
3 | * | |
4 | * Copyright (C) 2004 Thibaut VARENE <varenet@parisc-linux.org> | |
5 | * | |
6 | * Some code stollen from alpha & sparc64 ;) | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | * | |
22 | * We use pdc_printf() throughout the file for all output messages, to avoid | |
23 | * losing messages because of disabled interrupts. Since we're using these | |
24 | * messages for debugging purposes, it makes sense not to send them to the | |
25 | * linux console. | |
26 | */ | |
27 | ||
28 | ||
29 | #include <linux/config.h> | |
30 | #include <linux/kernel.h> | |
31 | #include <linux/sched.h> | |
32 | #include <linux/spinlock.h> | |
33 | #include <linux/hardirq.h> /* in_interrupt() */ | |
34 | #include <asm/system.h> | |
35 | #include <asm/hardirq.h> /* in_interrupt() */ | |
36 | #include <asm/pdc.h> | |
37 | ||
38 | #undef INIT_STUCK | |
39 | #define INIT_STUCK 1L << 30 | |
40 | ||
41 | #ifdef CONFIG_DEBUG_SPINLOCK | |
42 | ||
43 | ||
44 | void _dbg_spin_lock(spinlock_t * lock, const char *base_file, int line_no) | |
45 | { | |
46 | volatile unsigned int *a; | |
47 | long stuck = INIT_STUCK; | |
48 | void *inline_pc = __builtin_return_address(0); | |
49 | unsigned long started = jiffies; | |
50 | int printed = 0; | |
51 | int cpu = smp_processor_id(); | |
52 | ||
53 | try_again: | |
54 | ||
55 | /* Do the actual locking */ | |
56 | /* <T-Bone> ggg: we can't get stuck on the outter loop? | |
57 | * <ggg> T-Bone: We can hit the outer loop | |
58 | * alot if multiple CPUs are constantly racing for a lock | |
59 | * and the backplane is NOT fair about which CPU sees | |
60 | * the update first. But it won't hang since every failed | |
61 | * attempt will drop us back into the inner loop and | |
62 | * decrement `stuck'. | |
63 | * <ggg> K-class and some of the others are NOT fair in the HW | |
64 | * implementation so we could see false positives. | |
65 | * But fixing the lock contention is easier than | |
66 | * fixing the HW to be fair. | |
67 | * <tausq> __ldcw() returns 1 if we get the lock; otherwise we | |
68 | * spin until the value of the lock changes, or we time out. | |
69 | */ | |
70 | mb(); | |
71 | a = __ldcw_align(lock); | |
72 | while (stuck && (__ldcw(a) == 0)) | |
73 | while ((*a == 0) && --stuck); | |
74 | mb(); | |
75 | ||
76 | if (unlikely(stuck <= 0)) { | |
77 | pdc_printf( | |
78 | "%s:%d: spin_lock(%s/%p) stuck in %s at %p(%d)" | |
79 | " owned by %s:%d in %s at %p(%d)\n", | |
80 | base_file, line_no, lock->module, lock, | |
81 | current->comm, inline_pc, cpu, | |
82 | lock->bfile, lock->bline, lock->task->comm, | |
83 | lock->previous, lock->oncpu); | |
84 | stuck = INIT_STUCK; | |
85 | printed = 1; | |
86 | goto try_again; | |
87 | } | |
88 | ||
89 | /* Exiting. Got the lock. */ | |
90 | lock->oncpu = cpu; | |
91 | lock->previous = inline_pc; | |
92 | lock->task = current; | |
93 | lock->bfile = (char *)base_file; | |
94 | lock->bline = line_no; | |
95 | ||
96 | if (unlikely(printed)) { | |
97 | pdc_printf( | |
98 | "%s:%d: spin_lock grabbed in %s at %p(%d) %ld ticks\n", | |
99 | base_file, line_no, current->comm, inline_pc, | |
100 | cpu, jiffies - started); | |
101 | } | |
102 | } | |
103 | ||
104 | void _dbg_spin_unlock(spinlock_t * lock, const char *base_file, int line_no) | |
105 | { | |
106 | CHECK_LOCK(lock); | |
107 | volatile unsigned int *a; | |
108 | mb(); | |
109 | a = __ldcw_align(lock); | |
110 | if (unlikely((*a != 0) && lock->babble)) { | |
111 | lock->babble--; | |
112 | pdc_printf( | |
113 | "%s:%d: spin_unlock(%s:%p) not locked\n", | |
114 | base_file, line_no, lock->module, lock); | |
115 | } | |
116 | *a = 1; | |
117 | mb(); | |
118 | } | |
119 | ||
120 | int _dbg_spin_trylock(spinlock_t * lock, const char *base_file, int line_no) | |
121 | { | |
122 | int ret; | |
123 | volatile unsigned int *a; | |
124 | mb(); | |
125 | a = __ldcw_align(lock); | |
126 | ret = (__ldcw(a) != 0); | |
127 | mb(); | |
128 | if (ret) { | |
129 | lock->oncpu = smp_processor_id(); | |
130 | lock->previous = __builtin_return_address(0); | |
131 | lock->task = current; | |
132 | } else { | |
133 | lock->bfile = (char *)base_file; | |
134 | lock->bline = line_no; | |
135 | } | |
136 | return ret; | |
137 | } | |
138 | ||
139 | #endif /* CONFIG_DEBUG_SPINLOCK */ | |
140 | ||
141 | #ifdef CONFIG_DEBUG_RWLOCK | |
142 | ||
143 | /* Interrupts trouble detailed explanation, thx Grant: | |
144 | * | |
145 | * o writer (wants to modify data) attempts to acquire the rwlock | |
146 | * o He gets the write lock. | |
147 | * o Interupts are still enabled, we take an interrupt with the | |
148 | * write still holding the lock. | |
149 | * o interrupt handler tries to acquire the rwlock for read. | |
150 | * o deadlock since the writer can't release it at this point. | |
151 | * | |
152 | * In general, any use of spinlocks that competes between "base" | |
153 | * level and interrupt level code will risk deadlock. Interrupts | |
154 | * need to be disabled in the base level routines to avoid it. | |
155 | * Or more precisely, only the IRQ the base level routine | |
156 | * is competing with for the lock. But it's more efficient/faster | |
157 | * to just disable all interrupts on that CPU to guarantee | |
158 | * once it gets the lock it can release it quickly too. | |
159 | */ | |
160 | ||
161 | void _dbg_write_lock(rwlock_t *rw, const char *bfile, int bline) | |
162 | { | |
163 | void *inline_pc = __builtin_return_address(0); | |
164 | unsigned long started = jiffies; | |
165 | long stuck = INIT_STUCK; | |
166 | int printed = 0; | |
167 | int cpu = smp_processor_id(); | |
168 | ||
169 | if(unlikely(in_interrupt())) { /* acquiring write lock in interrupt context, bad idea */ | |
170 | pdc_printf("write_lock caller: %s:%d, IRQs enabled,\n", bfile, bline); | |
171 | BUG(); | |
172 | } | |
173 | ||
174 | /* Note: if interrupts are disabled (which is most likely), the printk | |
175 | will never show on the console. We might need a polling method to flush | |
176 | the dmesg buffer anyhow. */ | |
177 | ||
178 | retry: | |
179 | _raw_spin_lock(&rw->lock); | |
180 | ||
181 | if(rw->counter != 0) { | |
182 | /* this basically never happens */ | |
183 | _raw_spin_unlock(&rw->lock); | |
184 | ||
185 | stuck--; | |
186 | if ((unlikely(stuck <= 0)) && (rw->counter < 0)) { | |
187 | pdc_printf( | |
188 | "%s:%d: write_lock stuck on writer" | |
189 | " in %s at %p(%d) %ld ticks\n", | |
190 | bfile, bline, current->comm, inline_pc, | |
191 | cpu, jiffies - started); | |
192 | stuck = INIT_STUCK; | |
193 | printed = 1; | |
194 | } | |
195 | else if (unlikely(stuck <= 0)) { | |
196 | pdc_printf( | |
197 | "%s:%d: write_lock stuck on reader" | |
198 | " in %s at %p(%d) %ld ticks\n", | |
199 | bfile, bline, current->comm, inline_pc, | |
200 | cpu, jiffies - started); | |
201 | stuck = INIT_STUCK; | |
202 | printed = 1; | |
203 | } | |
204 | ||
205 | while(rw->counter != 0); | |
206 | ||
207 | goto retry; | |
208 | } | |
209 | ||
210 | /* got it. now leave without unlocking */ | |
211 | rw->counter = -1; /* remember we are locked */ | |
212 | ||
213 | if (unlikely(printed)) { | |
214 | pdc_printf( | |
215 | "%s:%d: write_lock grabbed in %s at %p(%d) %ld ticks\n", | |
216 | bfile, bline, current->comm, inline_pc, | |
217 | cpu, jiffies - started); | |
218 | } | |
219 | } | |
220 | ||
221 | int _dbg_write_trylock(rwlock_t *rw, const char *bfile, int bline) | |
222 | { | |
223 | #if 0 | |
224 | void *inline_pc = __builtin_return_address(0); | |
225 | int cpu = smp_processor_id(); | |
226 | #endif | |
227 | ||
228 | if(unlikely(in_interrupt())) { /* acquiring write lock in interrupt context, bad idea */ | |
229 | pdc_printf("write_lock caller: %s:%d, IRQs enabled,\n", bfile, bline); | |
230 | BUG(); | |
231 | } | |
232 | ||
233 | /* Note: if interrupts are disabled (which is most likely), the printk | |
234 | will never show on the console. We might need a polling method to flush | |
235 | the dmesg buffer anyhow. */ | |
236 | ||
237 | _raw_spin_lock(&rw->lock); | |
238 | ||
239 | if(rw->counter != 0) { | |
240 | /* this basically never happens */ | |
241 | _raw_spin_unlock(&rw->lock); | |
242 | return 0; | |
243 | } | |
244 | ||
245 | /* got it. now leave without unlocking */ | |
246 | rw->counter = -1; /* remember we are locked */ | |
247 | #if 0 | |
248 | pdc_printf("%s:%d: try write_lock grabbed in %s at %p(%d)\n", | |
249 | bfile, bline, current->comm, inline_pc, cpu); | |
250 | #endif | |
251 | return 1; | |
252 | } | |
253 | ||
254 | void _dbg_read_lock(rwlock_t * rw, const char *bfile, int bline) | |
255 | { | |
256 | #if 0 | |
257 | void *inline_pc = __builtin_return_address(0); | |
258 | unsigned long started = jiffies; | |
259 | int cpu = smp_processor_id(); | |
260 | #endif | |
261 | unsigned long flags; | |
262 | ||
263 | local_irq_save(flags); | |
264 | _raw_spin_lock(&rw->lock); | |
265 | ||
266 | rw->counter++; | |
267 | #if 0 | |
268 | pdc_printf( | |
269 | "%s:%d: read_lock grabbed in %s at %p(%d) %ld ticks\n", | |
270 | bfile, bline, current->comm, inline_pc, | |
271 | cpu, jiffies - started); | |
272 | #endif | |
273 | _raw_spin_unlock(&rw->lock); | |
274 | local_irq_restore(flags); | |
275 | } | |
276 | ||
277 | #endif /* CONFIG_DEBUG_RWLOCK */ |