Linus TorvaldsLinux-2.6.12-rc21da177e21 years agoHistory
277 lines · 7.7 KB · C
RawDownload
/*  *    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 */