arch/i386/mm/pageattr.c | 6 include/linux/mm.h | 5 include/linux/rtmutex.h | 15 + kernel/Makefile | 1 kernel/exit.c | 1 kernel/rtmutex-debug.c | 465 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 8 mm/page_alloc.c | 5 mm/slab.c | 2 9 files changed, 503 insertions(+), 5 deletions(-) Index: linux-futex.q/arch/i386/mm/pageattr.c =================================================================== --- linux-futex.q.orig/arch/i386/mm/pageattr.c +++ linux-futex.q/arch/i386/mm/pageattr.c @@ -222,10 +222,12 @@ void kernel_map_pages(struct page *page, { if (PageHighMem(page)) return; - if (!enable) + if (!enable) { mutex_debug_check_no_locks_freed(page_address(page), numpages * PAGE_SIZE); - + rt_mutex_debug_check_no_locks_freed(page_address(page), + page_address(page+numpages)); + } /* the return value is ignored - the calls cannot fail, * large pages are disabled at boot time. */ Index: linux-futex.q/include/linux/mm.h =================================================================== --- linux-futex.q.orig/include/linux/mm.h +++ linux-futex.q/include/linux/mm.h @@ -1026,9 +1026,12 @@ static inline void vm_stat_account(struc static inline void kernel_map_pages(struct page *page, int numpages, int enable) { - if (!PageHighMem(page) && !enable) + if (!PageHighMem(page) && !enable) { mutex_debug_check_no_locks_freed(page_address(page), numpages * PAGE_SIZE); + rt_mutex_debug_check_no_locks_freed(page_address(page), + page_address(page+numpages)); + } } #endif Index: linux-futex.q/include/linux/rtmutex.h =================================================================== --- linux-futex.q.orig/include/linux/rtmutex.h +++ linux-futex.q/include/linux/rtmutex.h @@ -56,6 +56,19 @@ struct rt_mutex_waiter { }; #ifdef CONFIG_DEBUG_RT_MUTEXES + extern int rt_mutex_debug_check_no_locks_freed(const void *from, + const void *to); + extern void rt_mutex_debug_check_no_locks_held(struct task_struct *task); +#else + static inline int rt_mutex_debug_check_no_locks_freed(const void *from, + const void *to) + { + return 0; + } +# define rt_mutex_debug_check_no_locks_held(task) do { } while (0) +#endif + +#ifdef CONFIG_DEBUG_RT_MUTEXES # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) \ , .name = #mutexname, .file = __FILE__, .line = __LINE__ # define rt_mutex_init(mutex) __rt_mutex_init(mutex, __FUNCTION__) @@ -63,7 +76,7 @@ struct rt_mutex_waiter { #else # define __DEBUG_RT_MUTEX_INITIALIZER(mutexname) # define rt_mutex_init(mutex) __rt_mutex_init(mutex, NULL) -# define rt_mutex_debug_task_free(t) do { } while (0) +# define rt_mutex_debug_task_free(t) do { } while (0) #endif #define __RT_MUTEX_INITIALIZER(mutexname) \ Index: linux-futex.q/kernel/Makefile =================================================================== --- linux-futex.q.orig/kernel/Makefile +++ linux-futex.q/kernel/Makefile @@ -16,6 +16,7 @@ ifeq ($(CONFIG_COMPAT),y) obj-$(CONFIG_FUTEX) += futex_compat.o endif obj-$(CONFIG_RT_MUTEXES) += rtmutex.o +obj-$(CONFIG_DEBUG_RT_MUTEXES) += rtmutex-debug.o obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o obj-$(CONFIG_SMP) += cpu.o spinlock.o obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock.o Index: linux-futex.q/kernel/exit.c =================================================================== --- linux-futex.q.orig/kernel/exit.c +++ linux-futex.q/kernel/exit.c @@ -888,6 +888,7 @@ fastcall NORET_TYPE void do_exit(long co * If DEBUG_MUTEXES is on, make sure we are holding no locks: */ mutex_debug_check_no_locks_held(tsk); + rt_mutex_debug_check_no_locks_held(tsk); /* PF_DEAD causes final put_task_struct after we schedule. */ preempt_disable(); Index: linux-futex.q/kernel/rtmutex-debug.c =================================================================== --- /dev/null +++ linux-futex.q/kernel/rtmutex-debug.c @@ -0,0 +1,465 @@ +/* + * RT-Mutexes: blocking mutual exclusion locks with PI support + * + * started by Ingo Molnar and Thomas Gleixner: + * + * Copyright (C) 2004-2006 Red Hat, Inc., Ingo Molnar + * Copyright (C) 2006, Timesys Corp., Thomas Gleixner + * + * This code is based on the rt.c implementation in the preempt-rt tree. + * Portions of said code are + * + * Copyright (C) 2004, LynuxWorks, Inc., Igor Manyilov, Bill Huey + * Copyright (C) 2001 David Howells (dhowells@redhat.com). + * Copyright (C) 2006 Esben Nielsen + * Copyright (C) 2006, Kihon Technologies Inc., + * Steven Rostedt + * + * See rt.c in preempt-rt for proper credits and further information + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtmutex.h" + + +# define TRACE_WARN_ON(x) WARN_ON(x) +# define TRACE_BUG_ON(x) BUG_ON(x) + +# define TRACE_OFF() \ +do { \ + if (rt_trace_on) { \ + rt_trace_on = 0; \ + console_verbose(); \ + if (spin_is_locked(¤t->pi_lock)) \ + _raw_spin_unlock(¤t->pi_lock); \ + spin_unlock(&tracelock); \ + } \ +} while (0) + +# define TRACE_BUG_LOCKED() \ +do { \ + TRACE_OFF(); \ + BUG(); \ +} while (0) + +# define TRACE_WARN_ON_LOCKED(c) \ +do { \ + if (unlikely(c)) { \ + TRACE_OFF(); \ + WARN_ON(1); \ + } \ +} while (0) + +# define TRACE_BUG_ON_LOCKED(c) \ +do { \ + if (unlikely(c)) \ + TRACE_BUG_LOCKED(); \ +} while (0) + +#ifdef CONFIG_SMP +# define SMP_TRACE_BUG_ON_LOCKED(c) TRACE_BUG_ON_LOCKED(c) +#else +# define SMP_TRACE_BUG_ON_LOCKED(c) do { } while (0) +#endif + + +/* + * We need a global lock when we walk through the multi-process + * lock tree... + */ +static spinlock_t tracelock = SPIN_LOCK_UNLOCKED; +static LIST_HEAD(held_locks); + +/* + * deadlock detection flag. We turn it off when we detect + * the first problem because we dont want to recurse back + * into the tracing code when doing error printk or + * executing a BUG(): + */ +int rt_trace_on = 1; + +void deadlock_trace_off(void) +{ + rt_trace_on = 0; +} + +static void printk_task(task_t *p) +{ + if (p) + printk("%16s:%5d [%p, %3d]", p->comm, p->pid, p, p->prio); + else + printk(""); +} + +static void printk_task_short(task_t *p) +{ + if (p) + printk("%s/%d [%p, %3d]", p->comm, p->pid, p, p->prio); + else + printk(""); +} + +static void printk_lock(struct rt_mutex *lock, int print_owner) +{ + if (lock->name) + printk(" [%p] {%s}\n", + lock, lock->name); + else + printk(" [%p] {%s:%d}\n", + lock, lock->file, lock->line); + + if (print_owner && rt_mutex_owner(lock)) { + printk(".. ->owner: %p\n", lock->owner); + printk(".. held by: "); + printk_task(rt_mutex_owner(lock)); + printk("\n"); + } + if (rt_mutex_owner(lock)) { + printk("... acquired at: "); + print_symbol("%s\n", lock->acquire_ip); + } +} + +static void printk_waiter(struct rt_mutex_waiter *w) +{ + printk("-------------------------\n"); + printk("| waiter struct %p:\n", w); + printk("| w->list_entry: [DP:%p/%p|SP:%p/%p|PRI:%d]\n", + w->list_entry.plist.prio_list.prev, w->list_entry.plist.prio_list.next, + w->list_entry.plist.node_list.prev, w->list_entry.plist.node_list.next, + w->list_entry.prio); + printk("| w->pi_list_entry: [DP:%p/%p|SP:%p/%p|PRI:%d]\n", + w->pi_list_entry.plist.prio_list.prev, w->pi_list_entry.plist.prio_list.next, + w->pi_list_entry.plist.node_list.prev, w->pi_list_entry.plist.node_list.next, + w->pi_list_entry.prio); + printk("\n| lock:\n"); + printk_lock(w->lock, 1); + printk("| w->ti->task:\n"); + printk_task(w->task); + printk("| blocked at: "); + print_symbol("%s\n", w->ip); + printk("-------------------------\n"); +} + +static void show_task_locks(task_t *p) +{ + switch (p->state) { + case TASK_RUNNING: printk("R"); break; + case TASK_INTERRUPTIBLE: printk("S"); break; + case TASK_UNINTERRUPTIBLE: printk("D"); break; + case TASK_STOPPED: printk("T"); break; + case EXIT_ZOMBIE: printk("Z"); break; + case EXIT_DEAD: printk("X"); break; + default: printk("?"); break; + } + printk_task(p); + if (p->pi_blocked_on) { + struct rt_mutex *lock = p->pi_blocked_on->lock; + + printk(" blocked on:"); + printk_lock(lock, 1); + } else + printk(" (not blocked)\n"); +} + +void rt_mutex_show_held_locks(task_t *filter) +{ + struct list_head *curr, *cursor = NULL; + struct rt_mutex *lock; + task_t *t; + unsigned long flags; + int count = 0; + + if (!rt_trace_on) + return; + + if (filter) { + printk("------------------------------\n"); + printk("| showing all locks held by: | ("); + printk_task_short(filter); + printk("):\n"); + printk("------------------------------\n"); + } else { + printk("---------------------------\n"); + printk("| showing all locks held: |\n"); + printk("---------------------------\n"); + } + + /* + * Play safe and acquire the global trace lock. We + * cannot printk with that lock held so we iterate + * very carefully: + */ +next: + spin_lock_irqsave(&tracelock, flags); + list_for_each(curr, &held_locks) { + if (cursor && curr != cursor) + continue; + lock = list_entry(curr, struct rt_mutex, held_list); + t = rt_mutex_owner(lock); + if (filter && (t != filter)) + continue; + count++; + cursor = curr->next; + spin_unlock_irqrestore(&tracelock, flags); + + printk("\n#%03d: ", count); + printk_lock(lock, filter ? 0 : 1); + goto next; + } + spin_unlock_irqrestore(&tracelock, flags); + printk("\n"); +} + +void rt_mutex_show_all_locks(void) +{ + task_t *g, *p; + int count = 10; + int unlock = 1; + + printk("\nshowing all tasks:\n"); + + /* + * Here we try to get the tasklist_lock as hard as possible, + * if not successful after 2 seconds we ignore it (but keep + * trying). This is to enable a debug printout even if a + * tasklist_lock-holding task deadlocks or crashes. + */ +retry: + if (!read_trylock(&tasklist_lock)) { + if (count == 10) + printk("hm, tasklist_lock locked, retrying... "); + if (count) { + count--; + printk(" #%d", 10-count); + mdelay(200); + goto retry; + } + printk(" ignoring it.\n"); + unlock = 0; + } + if (count != 10) + printk(" locked it.\n"); + + do_each_thread(g, p) { + show_task_locks(p); + if (!unlock) + if (read_trylock(&tasklist_lock)) + unlock = 1; + } while_each_thread(g, p); + + printk("\n"); + rt_mutex_show_held_locks(NULL); + printk("=============================================\n\n"); + + if (unlock) + read_unlock(&tasklist_lock); +} + +void rt_mutex_debug_check_no_locks_held(task_t *task) +{ + struct list_head *curr, *next, *cursor = NULL; + struct rt_mutex *lock; + struct rt_mutex_waiter *w; + unsigned long flags; + + if (!rt_trace_on) + return; + if (!rt_prio(task->normal_prio) && rt_prio(task->prio)) { + printk("BUG: PI priority boost leaked!\n"); + printk_task(task); + printk("\n"); + } +restart: + spin_lock_irqsave(&tracelock, flags); + list_for_each_safe(curr, next, &held_locks) { + if (cursor && curr != cursor) + continue; + lock = list_entry(curr, struct rt_mutex, held_list); + if(task != rt_mutex_owner(lock)) + continue; + cursor = next; + list_del_init(curr); + spin_unlock_irqrestore(&tracelock, flags); + + printk("BUG: %s/%d, lock held at task exit time!\n", + task->comm, task->pid); + printk_lock(lock, 1); + if (rt_mutex_owner(lock) != task) + printk("exiting task is not even the owner??\n"); + goto restart; + } + _raw_spin_lock(&task->pi_lock); + plist_for_each_entry(w, &task->pi_waiters, pi_list_entry) { + TRACE_OFF(); + + printk("hm, PI interest held at exit time? Task:\n"); + printk_task(task); + printk_waiter(w); + return; + } + _raw_spin_unlock(&task->pi_lock); + spin_unlock_irqrestore(&tracelock, flags); +} + +int rt_mutex_debug_check_no_locks_freed(const void *from, const void *to) +{ + struct list_head *curr, *next, *cursor = NULL; + struct rt_mutex *lock; + unsigned long flags; + void *lock_addr; + int err = 0; + + if (!rt_trace_on) + return err; +restart: + spin_lock_irqsave(&tracelock, flags); + list_for_each_safe(curr, next, &held_locks) { + if (cursor && curr != cursor) + continue; + lock = list_entry(curr, struct rt_mutex, held_list); + lock_addr = lock; + if (lock_addr < from || lock_addr >= to) + continue; + cursor = next; + list_del_init(curr); + TRACE_OFF(); + err = 1; + + printk("BUG: %s/%d, active lock [%p(%p-%p)] freed!\n", + current->comm, current->pid, lock, from, to); + dump_stack(); + printk_lock(lock, 1); + if (rt_mutex_owner(lock) != current) + printk("freeing task is not even the owner??\n"); + goto restart; + } + spin_unlock_irqrestore(&tracelock, flags); + + return err; +} + +void rt_mutex_debug_task_free(struct task_struct *tsk) +{ + WARN_ON(!plist_head_empty(&tsk->pi_waiters)); + WARN_ON(!list_empty(&tsk->pi_lock_chain)); + WARN_ON(tsk->pi_blocked_on); + WARN_ON(tsk->pi_locked_by); +} + +/* + * When deadlock detection is activated by the caller of the lock + * function, then the caller knows how to handle the -EDEADLK return. + */ +void debug_rt_mutex_deadlock(int detect, struct rt_mutex *act_lock, + struct rt_mutex *lock __IP_DECL__) +{ + struct task_struct *task = rt_mutex_owner(act_lock); + + if (!rt_trace_on || detect) + return; + + TRACE_OFF(); + + printk("\n============================================\n"); + printk( "[ BUG: circular locking deadlock detected! ]\n"); + printk( "--------------------------------------------\n"); + printk("%s/%d is deadlocking current task %s/%d\n\n", + task->comm, task->pid, current->comm, current->pid); + printk("\n1) %s/%d is trying to acquire this lock:\n", + current->comm, current->pid); + printk_lock(act_lock, 1); + + printk("... trying at: "); + print_symbol("%s\n", ip); + + printk("\n2) %s/%d is blocked on this lock:\n", task->comm, task->pid); + printk_lock(lock, 1); + + rt_mutex_show_held_locks(current); + rt_mutex_show_held_locks(task); + + printk("\n%s/%d's [blocked] stackdump:\n\n", task->comm, task->pid); + show_stack(task, NULL); + printk("\n%s/%d's [current] stackdump:\n\n", + current->comm, current->pid); + dump_stack(); + rt_mutex_show_all_locks(); + printk("[ turning off deadlock detection." + "Please report this trace. ]\n\n"); + local_irq_disable(); +} + +void debug_rt_mutex_lock(struct rt_mutex *lock __IP_DECL__) +{ + unsigned long flags; + + if (rt_trace_on) { + TRACE_WARN_ON_LOCKED(!list_empty(&lock->held_list)); + + spin_lock_irqsave(&tracelock, flags); + list_add_tail(&lock->held_list, &held_locks); + spin_unlock_irqrestore(&tracelock, flags); + + lock->acquire_ip = ip; + } +} + +void debug_rt_mutex_unlock(struct rt_mutex *lock) +{ + unsigned long flags; + + if (rt_trace_on) { + TRACE_WARN_ON_LOCKED(rt_mutex_owner(lock) != current); + TRACE_WARN_ON_LOCKED(list_empty(&lock->held_list)); + + spin_lock_irqsave(&tracelock, flags); + list_del_init(&lock->held_list); + spin_unlock_irqrestore(&tracelock, flags); + } +} + +void debug_rt_mutex_init_waiter(struct rt_mutex_waiter *waiter) +{ + memset(waiter, 0x11, sizeof(*waiter)); + plist_node_init(&waiter->list_entry, MAX_PRIO); + plist_node_init(&waiter->pi_list_entry, MAX_PRIO); +} + +void debug_rt_mutex_free_waiter(struct rt_mutex_waiter *waiter) +{ + TRACE_WARN_ON(!plist_node_empty(&waiter->list_entry)); + TRACE_WARN_ON(!plist_node_empty(&waiter->pi_list_entry)); + TRACE_WARN_ON(waiter->task); + memset(waiter, 0x22, sizeof(*waiter)); +} + +void debug_rt_mutex_init(struct rt_mutex *lock, const char *name) +{ + void *addr = lock; + + if (rt_trace_on) { + rt_mutex_debug_check_no_locks_freed(addr, addr + + sizeof(struct rt_mutex)); + INIT_LIST_HEAD(&lock->held_list); + lock->name = name; + } +} + +void rt_mutex_deadlock_account_lock(struct rt_mutex *lock, task_t *task) +{ +} + +void rt_mutex_deadlock_account_unlock(struct task_struct *task) +{ +} + Index: linux-futex.q/lib/Kconfig.debug =================================================================== --- linux-futex.q.orig/lib/Kconfig.debug +++ linux-futex.q/lib/Kconfig.debug @@ -103,6 +103,14 @@ config DEBUG_MUTEXES This allows mutex semantics violations and mutex related deadlocks (lockups) to be detected and reported automatically. +config DEBUG_RT_MUTEXES + bool "RT Mutex debugging, deadlock detection" + default y + depends on DEBUG_KERNEL && RT_MUTEXES + help + This allows rt mutex semantics violations and rt mutex related + deadlocks (lockups) to be detected and reported automatically. + config DEBUG_SPINLOCK bool "Spinlock debugging" depends on DEBUG_KERNEL Index: linux-futex.q/mm/page_alloc.c =================================================================== --- linux-futex.q.orig/mm/page_alloc.c +++ linux-futex.q/mm/page_alloc.c @@ -419,9 +419,12 @@ static void __free_pages_ok(struct page int reserved = 0; arch_free_page(page, order); - if (!PageHighMem(page)) + if (!PageHighMem(page)) { mutex_debug_check_no_locks_freed(page_address(page), PAGE_SIZE< #include #include +#include #include #include @@ -3286,6 +3287,7 @@ void kfree(const void *objp) kfree_debugcheck(objp); c = virt_to_cache(objp); mutex_debug_check_no_locks_freed(objp, obj_size(c)); + rt_mutex_debug_check_no_locks_freed(objp, objp + obj_size(c)); __cache_free(c, (void *)objp); local_irq_restore(flags); }