diff options
Diffstat (limited to 'arch/parisc/lib/debuglocks.c')
-rw-r--r-- | arch/parisc/lib/debuglocks.c | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/arch/parisc/lib/debuglocks.c b/arch/parisc/lib/debuglocks.c new file mode 100644 index 00000000000..1b33fe6e5b7 --- /dev/null +++ b/arch/parisc/lib/debuglocks.c @@ -0,0 +1,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 */ |