Commit 93dae5b7 authored by David S. Miller's avatar David S. Miller

sparc64: Add global register dumping facility.

When a cpu really is stuck in the kernel, it can be often
impossible to figure out which cpu is stuck where.  The
worst case is when the stuck cpu has interrupts disabled.

Therefore, implement a global cpu state capture that uses
SMP message interrupts which are not disabled by the
normal IRQ enable/disable APIs of the kernel.

As long as we can get a sysrq 'y' to the kernel, we can
get a dump.  Even if the console interrupt cpu is wedged,
we can trigger it from userspace using /proc/sysrq-trigger

The output is made compact so that this facility is more
useful on high cpu count systems, which is where this
facility will likely find itself the most useful :)
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 88278ca2
/* arch/sparc64/kernel/process.c /* arch/sparc64/kernel/process.c
* *
* Copyright (C) 1995, 1996 David S. Miller (davem@caip.rutgers.edu) * Copyright (C) 1995, 1996, 2008 David S. Miller (davem@davemloft.net)
* Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be)
* Copyright (C) 1997, 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) * Copyright (C) 1997, 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz)
*/ */
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/cpu.h> #include <linux/cpu.h>
#include <linux/elfcore.h> #include <linux/elfcore.h>
#include <linux/sysrq.h>
#include <asm/oplib.h> #include <asm/oplib.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
...@@ -49,6 +50,8 @@ ...@@ -49,6 +50,8 @@
#include <asm/sstate.h> #include <asm/sstate.h>
#include <asm/reboot.h> #include <asm/reboot.h>
#include <asm/syscalls.h> #include <asm/syscalls.h>
#include <asm/irq_regs.h>
#include <asm/smp.h>
/* #define VERBOSE_SHOWREGS */ /* #define VERBOSE_SHOWREGS */
...@@ -298,6 +301,118 @@ void show_regs(struct pt_regs *regs) ...@@ -298,6 +301,118 @@ void show_regs(struct pt_regs *regs)
#endif #endif
} }
#ifdef CONFIG_MAGIC_SYSRQ
struct global_reg_snapshot global_reg_snapshot[NR_CPUS];
static DEFINE_SPINLOCK(global_reg_snapshot_lock);
static void __global_reg_self(struct thread_info *tp, struct pt_regs *regs,
int this_cpu)
{
flushw_all();
global_reg_snapshot[this_cpu].tstate = regs->tstate;
global_reg_snapshot[this_cpu].tpc = regs->tpc;
global_reg_snapshot[this_cpu].tnpc = regs->tnpc;
global_reg_snapshot[this_cpu].o7 = regs->u_regs[UREG_I7];
if (regs->tstate & TSTATE_PRIV) {
struct reg_window *rw;
rw = (struct reg_window *)
(regs->u_regs[UREG_FP] + STACK_BIAS);
global_reg_snapshot[this_cpu].i7 = rw->ins[6];
} else
global_reg_snapshot[this_cpu].i7 = 0;
global_reg_snapshot[this_cpu].thread = tp;
}
/* In order to avoid hangs we do not try to synchronize with the
* global register dump client cpus. The last store they make is to
* the thread pointer, so do a short poll waiting for that to become
* non-NULL.
*/
static void __global_reg_poll(struct global_reg_snapshot *gp)
{
int limit = 0;
while (!gp->thread && ++limit < 100) {
barrier();
udelay(1);
}
}
static void sysrq_handle_globreg(int key, struct tty_struct *tty)
{
struct thread_info *tp = current_thread_info();
struct pt_regs *regs = get_irq_regs();
#ifdef CONFIG_KALLSYMS
char buffer[KSYM_SYMBOL_LEN];
#endif
unsigned long flags;
int this_cpu, cpu;
if (!regs)
regs = tp->kregs;
spin_lock_irqsave(&global_reg_snapshot_lock, flags);
memset(global_reg_snapshot, 0, sizeof(global_reg_snapshot));
this_cpu = raw_smp_processor_id();
__global_reg_self(tp, regs, this_cpu);
smp_fetch_global_regs();
for_each_online_cpu(cpu) {
struct global_reg_snapshot *gp = &global_reg_snapshot[cpu];
struct thread_info *tp;
__global_reg_poll(gp);
tp = gp->thread;
printk("%c CPU[%3d]: TSTATE[%016lx] TPC[%016lx] TNPC[%016lx] TASK[%s:%d]\n",
(cpu == this_cpu ? '*' : ' '), cpu,
gp->tstate, gp->tpc, gp->tnpc,
((tp && tp->task) ? tp->task->comm : "NULL"),
((tp && tp->task) ? tp->task->pid : -1));
#ifdef CONFIG_KALLSYMS
if (gp->tstate & TSTATE_PRIV) {
sprint_symbol(buffer, gp->tpc);
printk(" TPC[%s] ", buffer);
sprint_symbol(buffer, gp->o7);
printk("O7[%s] ", buffer);
sprint_symbol(buffer, gp->i7);
printk("I7[%s]\n", buffer);
} else
#endif
{
printk(" TPC[%lx] O7[%lx] I7[%lx]\n",
gp->tpc, gp->o7, gp->i7);
}
}
memset(global_reg_snapshot, 0, sizeof(global_reg_snapshot));
spin_unlock_irqrestore(&global_reg_snapshot_lock, flags);
}
static struct sysrq_key_op sparc_globalreg_op = {
.handler = sysrq_handle_globreg,
.help_msg = "Globalregs",
.action_msg = "Show Global CPU Regs",
};
static int __init sparc_globreg_init(void)
{
return register_sysrq_key('y', &sparc_globalreg_op);
}
core_initcall(sparc_globreg_init);
#endif
unsigned long thread_saved_pc(struct task_struct *tsk) unsigned long thread_saved_pc(struct task_struct *tsk)
{ {
struct thread_info *ti = task_thread_info(tsk); struct thread_info *ti = task_thread_info(tsk);
......
...@@ -900,6 +900,9 @@ extern unsigned long xcall_flush_tlb_mm; ...@@ -900,6 +900,9 @@ extern unsigned long xcall_flush_tlb_mm;
extern unsigned long xcall_flush_tlb_pending; extern unsigned long xcall_flush_tlb_pending;
extern unsigned long xcall_flush_tlb_kernel_range; extern unsigned long xcall_flush_tlb_kernel_range;
extern unsigned long xcall_report_regs; extern unsigned long xcall_report_regs;
#ifdef CONFIG_MAGIC_SYSRQ
extern unsigned long xcall_fetch_glob_regs;
#endif
extern unsigned long xcall_receive_signal; extern unsigned long xcall_receive_signal;
extern unsigned long xcall_new_mmu_context_version; extern unsigned long xcall_new_mmu_context_version;
#ifdef CONFIG_KGDB #ifdef CONFIG_KGDB
...@@ -1080,6 +1083,13 @@ void smp_report_regs(void) ...@@ -1080,6 +1083,13 @@ void smp_report_regs(void)
smp_cross_call(&xcall_report_regs, 0, 0, 0); smp_cross_call(&xcall_report_regs, 0, 0, 0);
} }
#ifdef CONFIG_MAGIC_SYSRQ
void smp_fetch_global_regs(void)
{
smp_cross_call(&xcall_fetch_glob_regs, 0, 0, 0);
}
#endif
/* We know that the window frames of the user have been flushed /* We know that the window frames of the user have been flushed
* to the stack before we get here because all callers of us * to the stack before we get here because all callers of us
* are flush_tlb_*() routines, and these run after flush_cache_*() * are flush_tlb_*() routines, and these run after flush_cache_*()
......
/* /*
* ultra.S: Don't expand these all over the place... * ultra.S: Don't expand these all over the place...
* *
* Copyright (C) 1997, 2000 David S. Miller (davem@redhat.com) * Copyright (C) 1997, 2000, 2008 David S. Miller (davem@davemloft.net)
*/ */
#include <asm/asi.h> #include <asm/asi.h>
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <asm/thread_info.h> #include <asm/thread_info.h>
#include <asm/cacheflush.h> #include <asm/cacheflush.h>
#include <asm/hypervisor.h> #include <asm/hypervisor.h>
#include <asm/cpudata.h>
/* Basically, most of the Spitfire vs. Cheetah madness /* Basically, most of the Spitfire vs. Cheetah madness
* has to do with the fact that Cheetah does not support * has to do with the fact that Cheetah does not support
...@@ -514,6 +515,32 @@ xcall_report_regs: ...@@ -514,6 +515,32 @@ xcall_report_regs:
b rtrap_xcall b rtrap_xcall
ldx [%sp + PTREGS_OFF + PT_V9_TSTATE], %l1 ldx [%sp + PTREGS_OFF + PT_V9_TSTATE], %l1
#ifdef CONFIG_MAGIC_SYSRQ
.globl xcall_fetch_glob_regs
xcall_fetch_glob_regs:
sethi %hi(global_reg_snapshot), %g1
or %g1, %lo(global_reg_snapshot), %g1
__GET_CPUID(%g2)
sllx %g2, 6, %g3
add %g1, %g3, %g1
rdpr %tstate, %g7
stx %g7, [%g1 + GR_SNAP_TSTATE]
rdpr %tpc, %g7
stx %g7, [%g1 + GR_SNAP_TPC]
rdpr %tnpc, %g7
stx %g7, [%g1 + GR_SNAP_TNPC]
stx %o7, [%g1 + GR_SNAP_O7]
stx %i7, [%g1 + GR_SNAP_I7]
sethi %hi(trap_block), %g7
or %g7, %lo(trap_block), %g7
sllx %g2, TRAP_BLOCK_SZ_SHIFT, %g2
add %g7, %g2, %g7
ldx [%g7 + TRAP_PER_CPU_THREAD], %g3
membar #StoreStore
stx %g3, [%g1 + GR_SNAP_THREAD]
retry
#endif /* CONFIG_MAGIC_SYSRQ */
#ifdef DCACHE_ALIASING_POSSIBLE #ifdef DCACHE_ALIASING_POSSIBLE
.align 32 .align 32
.globl xcall_flush_dcache_page_cheetah .globl xcall_flush_dcache_page_cheetah
......
...@@ -402,6 +402,7 @@ static struct sysrq_key_op *sysrq_key_table[36] = { ...@@ -402,6 +402,7 @@ static struct sysrq_key_op *sysrq_key_table[36] = {
&sysrq_showstate_blocked_op, /* w */ &sysrq_showstate_blocked_op, /* w */
/* x: May be registered on ppc/powerpc for xmon */ /* x: May be registered on ppc/powerpc for xmon */
NULL, /* x */ NULL, /* x */
/* y: May be registered on sparc64 for global register dump */
NULL, /* y */ NULL, /* y */
NULL /* z */ NULL /* z */
}; };
......
...@@ -126,6 +126,17 @@ struct sparc_trapf { ...@@ -126,6 +126,17 @@ struct sparc_trapf {
#define TRACEREG32_SZ sizeof(struct pt_regs32) #define TRACEREG32_SZ sizeof(struct pt_regs32)
#define STACKFRAME32_SZ sizeof(struct sparc_stackf32) #define STACKFRAME32_SZ sizeof(struct sparc_stackf32)
struct global_reg_snapshot {
unsigned long tstate;
unsigned long tpc;
unsigned long tnpc;
unsigned long o7;
unsigned long i7;
struct thread_info *thread;
unsigned long pad1;
unsigned long pad2;
};
#ifdef __KERNEL__ #ifdef __KERNEL__
#define __ARCH_WANT_COMPAT_SYS_PTRACE #define __ARCH_WANT_COMPAT_SYS_PTRACE
...@@ -295,6 +306,16 @@ extern void __show_regs(struct pt_regs *); ...@@ -295,6 +306,16 @@ extern void __show_regs(struct pt_regs *);
#define SF_XARG5 0x58 #define SF_XARG5 0x58
#define SF_XXARG 0x5c #define SF_XXARG 0x5c
/* global_reg_snapshot offsets */
#define GR_SNAP_TSTATE 0x00
#define GR_SNAP_TPC 0x08
#define GR_SNAP_TNPC 0x10
#define GR_SNAP_O7 0x18
#define GR_SNAP_I7 0x20
#define GR_SNAP_THREAD 0x28
#define GR_SNAP_PAD1 0x30
#define GR_SNAP_PAD2 0x38
/* Stuff for the ptrace system call */ /* Stuff for the ptrace system call */
#define PTRACE_SPARC_DETACH 11 #define PTRACE_SPARC_DETACH 11
#define PTRACE_GETREGS 12 #define PTRACE_GETREGS 12
......
/* smp.h: Sparc64 specific SMP stuff. /* smp.h: Sparc64 specific SMP stuff.
* *
* Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) * Copyright (C) 1996, 2008 David S. Miller (davem@davemloft.net)
*/ */
#ifndef _SPARC64_SMP_H #ifndef _SPARC64_SMP_H
...@@ -44,6 +44,8 @@ extern int hard_smp_processor_id(void); ...@@ -44,6 +44,8 @@ extern int hard_smp_processor_id(void);
extern void smp_fill_in_sib_core_maps(void); extern void smp_fill_in_sib_core_maps(void);
extern void cpu_play_dead(void); extern void cpu_play_dead(void);
extern void smp_fetch_global_regs(void);
#ifdef CONFIG_HOTPLUG_CPU #ifdef CONFIG_HOTPLUG_CPU
extern int __cpu_disable(void); extern int __cpu_disable(void);
extern void __cpu_die(unsigned int cpu); extern void __cpu_die(unsigned int cpu);
...@@ -55,6 +57,7 @@ extern void __cpu_die(unsigned int cpu); ...@@ -55,6 +57,7 @@ extern void __cpu_die(unsigned int cpu);
#define hard_smp_processor_id() 0 #define hard_smp_processor_id() 0
#define smp_fill_in_sib_core_maps() do { } while (0) #define smp_fill_in_sib_core_maps() do { } while (0)
#define smp_fetch_global_regs() do { } while (0)
#endif /* !(CONFIG_SMP) */ #endif /* !(CONFIG_SMP) */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment