summaryrefslogtreecommitdiff
path: root/arch/parisc/lib/debuglocks.c
blob: 1b33fe6e5b7a145331d960c0d0181c0e384d9fe1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* 
 *    Debugging versions of SMP locking primitives.
 *
 *    Copyright (C) 2004 Thibaut VARENE <varenet@parisc-linux.org>
 *
 *    Some code stollen from alpha & sparc64 ;)
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    We use pdc_printf() throughout the file for all output messages, to avoid
 *    losing messages because of disabled interrupts. Since we're using these
 *    messages for debugging purposes, it makes sense not to send them to the
 *    linux console.
 */


#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/hardirq.h>	/* in_interrupt() */
#include <asm/system.h>
#include <asm/hardirq.h>	/* in_interrupt() */
#include <asm/pdc.h>

#undef INIT_STUCK
#define INIT_STUCK 1L << 30

#ifdef CONFIG_DEBUG_SPINLOCK


void _dbg_spin_lock(spinlock_t * lock, const char *base_file, int line_no)
{
	volatile unsigned int *a;
	long stuck = INIT_STUCK;
	void *inline_pc = __builtin_return_address(0);
	unsigned long started = jiffies;
	int printed = 0;
	int cpu = smp_processor_id();

try_again:

	/* Do the actual locking */
	/* <T-Bone> ggg: we can't get stuck on the outter loop?
	 * <ggg> T-Bone: We can hit the outer loop
	 *	alot if multiple CPUs are constantly racing for a lock
	 *	and the backplane is NOT fair about which CPU sees
	 *	the update first. But it won't hang since every failed
	 *	attempt will drop us back into the inner loop and
	 *	decrement `stuck'.
	 * <ggg> K-class and some of the others are NOT fair in the HW
	 * 	implementation so we could see false positives.
	 * 	But fixing the lock contention is easier than
	 * 	fixing the HW to be fair.
	 * <tausq> __ldcw() returns 1 if we get the lock; otherwise we
	 * 	spin until the value of the lock changes, or we time out.
	 */
	mb();
	a = __ldcw_align(lock);
	while (stuck && (__ldcw(a) == 0))
		while ((*a == 0) && --stuck);
	mb();

	if (unlikely(stuck <= 0)) {
		pdc_printf(
			"%s:%d: spin_lock(%s/%p) stuck in %s at %p(%d)"
			" owned by %s:%d in %s at %p(%d)\n",
			base_file, line_no, lock->module, lock,
			current->comm, inline_pc, cpu,
			lock->bfile, lock->bline, lock->task->comm,
			lock->previous, lock->oncpu);
		stuck = INIT_STUCK;
		printed = 1;
		goto try_again;
	}

	/* Exiting.  Got the lock.  */
	lock->oncpu = cpu;
	lock->previous = inline_pc;
	lock->task = current;
	lock->bfile = (char *)base_file;
	lock->bline = line_no;

	if (unlikely(printed)) {
		pdc_printf(
			"%s:%d: spin_lock grabbed in %s at %p(%d) %ld ticks\n",
			base_file, line_no, current->comm, inline_pc,
			cpu, jiffies - started);
	}
}

void _dbg_spin_unlock(spinlock_t * lock, const char *base_file, int line_no)
{
	CHECK_LOCK(lock);
	volatile unsigned int *a;
	mb();
	a = __ldcw_align(lock);
	if (unlikely((*a != 0) && lock->babble)) {
		lock->babble--;
		pdc_printf(
			"%s:%d: spin_unlock(%s:%p) not locked\n",
			base_file, line_no, lock->module, lock);
	}
	*a = 1;	
	mb();
}

int _dbg_spin_trylock(spinlock_t * lock, const char *base_file, int line_no)
{
	int ret;
	volatile unsigned int *a;
	mb();
	a = __ldcw_align(lock);
	ret = (__ldcw(a) != 0);
	mb();
	if (ret) {
		lock->oncpu = smp_processor_id();
		lock->previous = __builtin_return_address(0);
		lock->task = current;
	} else {
		lock->bfile = (char *)base_file;
		lock->bline = line_no;
	}
	return ret;
}

#endif /* CONFIG_DEBUG_SPINLOCK */

#ifdef CONFIG_DEBUG_RWLOCK

/* Interrupts trouble detailed explanation, thx Grant:
 *
 * o writer (wants to modify data) attempts to acquire the rwlock
 * o He gets the write lock.
 * o Interupts are still enabled, we take an interrupt with the
 *   write still holding the lock.
 * o interrupt handler tries to acquire the rwlock for read.
 * o deadlock since the writer can't release it at this point.
 * 
 * In general, any use of spinlocks that competes between "base"
 * level and interrupt level code will risk deadlock. Interrupts
 * need to be disabled in the base level routines to avoid it.
 * Or more precisely, only the IRQ the base level routine
 * is competing with for the lock.  But it's more efficient/faster
 * to just disable all interrupts on that CPU to guarantee
 * once it gets the lock it can release it quickly too.
 */
 
void _dbg_write_lock(rwlock_t *rw, const char *bfile, int bline)
{
	void *inline_pc = __builtin_return_address(0);
	unsigned long started = jiffies;
	long stuck = INIT_STUCK;
	int printed = 0;
	int cpu = smp_processor_id();
	
	if(unlikely(in_interrupt())) {	/* acquiring write lock in interrupt context, bad idea */
		pdc_printf("write_lock caller: %s:%d, IRQs enabled,\n", bfile, bline);
		BUG();
	}

	/* Note: if interrupts are disabled (which is most likely), the printk
	will never show on the console. We might need a polling method to flush
	the dmesg buffer anyhow. */
	
retry:
	_raw_spin_lock(&rw->lock);

	if(rw->counter != 0) {
		/* this basically never happens */
		_raw_spin_unlock(&rw->lock);
		
		stuck--;
		if ((unlikely(stuck <= 0)) && (rw->counter < 0)) {
			pdc_printf(
				"%s:%d: write_lock stuck on writer"
				" in %s at %p(%d) %ld ticks\n",
				bfile, bline, current->comm, inline_pc,
				cpu, jiffies - started);
			stuck = INIT_STUCK;
			printed = 1;
		}
		else if (unlikely(stuck <= 0)) {
			pdc_printf(
				"%s:%d: write_lock stuck on reader"
				" in %s at %p(%d) %ld ticks\n",
				bfile, bline, current->comm, inline_pc,
				cpu, jiffies - started);
			stuck = INIT_STUCK;
			printed = 1;
		}
		
		while(rw->counter != 0);

		goto retry;
	}

	/* got it.  now leave without unlocking */
	rw->counter = -1; /* remember we are locked */

	if (unlikely(printed)) {
		pdc_printf(
			"%s:%d: write_lock grabbed in %s at %p(%d) %ld ticks\n",
			bfile, bline, current->comm, inline_pc,
			cpu, jiffies - started);
	}
}

int _dbg_write_trylock(rwlock_t *rw, const char *bfile, int bline)
{
#if 0
	void *inline_pc = __builtin_return_address(0);
	int cpu = smp_processor_id();
#endif
	
	if(unlikely(in_interrupt())) {	/* acquiring write lock in interrupt context, bad idea */
		pdc_printf("write_lock caller: %s:%d, IRQs enabled,\n", bfile, bline);
		BUG();
	}

	/* Note: if interrupts are disabled (which is most likely), the printk
	will never show on the console. We might need a polling method to flush
	the dmesg buffer anyhow. */
	
	_raw_spin_lock(&rw->lock);

	if(rw->counter != 0) {
		/* this basically never happens */
		_raw_spin_unlock(&rw->lock);
		return 0;
	}

	/* got it.  now leave without unlocking */
	rw->counter = -1; /* remember we are locked */
#if 0
	pdc_printf("%s:%d: try write_lock grabbed in %s at %p(%d)\n",
		   bfile, bline, current->comm, inline_pc, cpu);
#endif
	return 1;
}

void _dbg_read_lock(rwlock_t * rw, const char *bfile, int bline)
{
#if 0
	void *inline_pc = __builtin_return_address(0);
	unsigned long started = jiffies;
	int cpu = smp_processor_id();
#endif
	unsigned long flags;

	local_irq_save(flags);
	_raw_spin_lock(&rw->lock); 

	rw->counter++;
#if 0
	pdc_printf(
		"%s:%d: read_lock grabbed in %s at %p(%d) %ld ticks\n",
		bfile, bline, current->comm, inline_pc,
		cpu, jiffies - started);
#endif
	_raw_spin_unlock(&rw->lock);
	local_irq_restore(flags);
}

#endif /* CONFIG_DEBUG_RWLOCK */