diff options
author | Ingo Molnar <mingo@elte.hu> | 2005-09-10 03:25:56 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-09-10 13:06:21 -0400 |
commit | fb1c8f93d869b34cacb8b8932e2b83d96a19d720 (patch) | |
tree | a006d078aa02e421a7dc4793c335308204859d36 /arch/parisc/lib | |
parent | 4327edf6b8a7ac7dce144313947995538842d8fd (diff) |
[PATCH] spinlock consolidation
This patch (written by me and also containing many suggestions of Arjan van
de Ven) does a major cleanup of the spinlock code. It does the following
things:
- consolidates and enhances the spinlock/rwlock debugging code
- simplifies the asm/spinlock.h files
- encapsulates the raw spinlock type and moves generic spinlock
features (such as ->break_lock) into the generic code.
- cleans up the spinlock code hierarchy to get rid of the spaghetti.
Most notably there's now only a single variant of the debugging code,
located in lib/spinlock_debug.c. (previously we had one SMP debugging
variant per architecture, plus a separate generic one for UP builds)
Also, i've enhanced the rwlock debugging facility, it will now track
write-owners. There is new spinlock-owner/CPU-tracking on SMP builds too.
All locks have lockup detection now, which will work for both soft and hard
spin/rwlock lockups.
The arch-level include files now only contain the minimally necessary
subset of the spinlock code - all the rest that can be generalized now
lives in the generic headers:
include/asm-i386/spinlock_types.h | 16
include/asm-x86_64/spinlock_types.h | 16
I have also split up the various spinlock variants into separate files,
making it easier to see which does what. The new layout is:
SMP | UP
----------------------------|-----------------------------------
asm/spinlock_types_smp.h | linux/spinlock_types_up.h
linux/spinlock_types.h | linux/spinlock_types.h
asm/spinlock_smp.h | linux/spinlock_up.h
linux/spinlock_api_smp.h | linux/spinlock_api_up.h
linux/spinlock.h | linux/spinlock.h
/*
* here's the role of the various spinlock/rwlock related include files:
*
* on SMP builds:
*
* asm/spinlock_types.h: contains the raw_spinlock_t/raw_rwlock_t and the
* initializers
*
* linux/spinlock_types.h:
* defines the generic type and initializers
*
* asm/spinlock.h: contains the __raw_spin_*()/etc. lowlevel
* implementations, mostly inline assembly code
*
* (also included on UP-debug builds:)
*
* linux/spinlock_api_smp.h:
* contains the prototypes for the _spin_*() APIs.
*
* linux/spinlock.h: builds the final spin_*() APIs.
*
* on UP builds:
*
* linux/spinlock_type_up.h:
* contains the generic, simplified UP spinlock type.
* (which is an empty structure on non-debug builds)
*
* linux/spinlock_types.h:
* defines the generic type and initializers
*
* linux/spinlock_up.h:
* contains the __raw_spin_*()/etc. version of UP
* builds. (which are NOPs on non-debug, non-preempt
* builds)
*
* (included on UP-non-debug builds:)
*
* linux/spinlock_api_up.h:
* builds the _spin_*() APIs.
*
* linux/spinlock.h: builds the final spin_*() APIs.
*/
All SMP and UP architectures are converted by this patch.
arm, i386, ia64, ppc, ppc64, s390/s390x, x64 was build-tested via
crosscompilers. m32r, mips, sh, sparc, have not been tested yet, but should
be mostly fine.
From: Grant Grundler <grundler@parisc-linux.org>
Booted and lightly tested on a500-44 (64-bit, SMP kernel, dual CPU).
Builds 32-bit SMP kernel (not booted or tested). I did not try to build
non-SMP kernels. That should be trivial to fix up later if necessary.
I converted bit ops atomic_hash lock to raw_spinlock_t. Doing so avoids
some ugly nesting of linux/*.h and asm/*.h files. Those particular locks
are well tested and contained entirely inside arch specific code. I do NOT
expect any new issues to arise with them.
If someone does ever need to use debug/metrics with them, then they will
need to unravel this hairball between spinlocks, atomic ops, and bit ops
that exist only because parisc has exactly one atomic instruction: LDCW
(load and clear word).
From: "Luck, Tony" <tony.luck@intel.com>
ia64 fix
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Arjan van de Ven <arjanv@infradead.org>
Signed-off-by: Grant Grundler <grundler@parisc-linux.org>
Cc: Matthew Wilcox <willy@debian.org>
Signed-off-by: Hirokazu Takata <takata@linux-m32r.org>
Signed-off-by: Mikael Pettersson <mikpe@csd.uu.se>
Signed-off-by: Benoit Boissinot <benoit.boissinot@ens-lyon.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'arch/parisc/lib')
-rw-r--r-- | arch/parisc/lib/Makefile | 2 | ||||
-rw-r--r-- | arch/parisc/lib/bitops.c | 4 | ||||
-rw-r--r-- | arch/parisc/lib/debuglocks.c | 277 |
3 files changed, 2 insertions, 281 deletions
diff --git a/arch/parisc/lib/Makefile b/arch/parisc/lib/Makefile index 7bf705676297..5f2e6904d14a 100644 --- a/arch/parisc/lib/Makefile +++ b/arch/parisc/lib/Makefile | |||
@@ -5,5 +5,3 @@ | |||
5 | lib-y := lusercopy.o bitops.o checksum.o io.o memset.o fixup.o memcpy.o | 5 | lib-y := lusercopy.o bitops.o checksum.o io.o memset.o fixup.o memcpy.o |
6 | 6 | ||
7 | obj-y := iomap.o | 7 | obj-y := iomap.o |
8 | |||
9 | lib-$(CONFIG_SMP) += debuglocks.o | ||
diff --git a/arch/parisc/lib/bitops.c b/arch/parisc/lib/bitops.c index 2de182f6fe8a..90f400b10282 100644 --- a/arch/parisc/lib/bitops.c +++ b/arch/parisc/lib/bitops.c | |||
@@ -13,8 +13,8 @@ | |||
13 | #include <asm/atomic.h> | 13 | #include <asm/atomic.h> |
14 | 14 | ||
15 | #ifdef CONFIG_SMP | 15 | #ifdef CONFIG_SMP |
16 | spinlock_t __atomic_hash[ATOMIC_HASH_SIZE] __lock_aligned = { | 16 | raw_spinlock_t __atomic_hash[ATOMIC_HASH_SIZE] __lock_aligned = { |
17 | [0 ... (ATOMIC_HASH_SIZE-1)] = SPIN_LOCK_UNLOCKED | 17 | [0 ... (ATOMIC_HASH_SIZE-1)] = __RAW_SPIN_LOCK_UNLOCKED |
18 | }; | 18 | }; |
19 | #endif | 19 | #endif |
20 | 20 | ||
diff --git a/arch/parisc/lib/debuglocks.c b/arch/parisc/lib/debuglocks.c deleted file mode 100644 index 1b33fe6e5b7a..000000000000 --- a/arch/parisc/lib/debuglocks.c +++ /dev/null | |||
@@ -1,277 +0,0 @@ | |||
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 */ | ||