Browse Source

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: David S. Miller <davem@davemloft.net>
David S. Miller 17 years ago
parent
commit
93dae5b70e

+ 116 - 1
arch/sparc64/kernel/process.c

@@ -1,6 +1,6 @@
 /*  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 @@
 #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 @@
 #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)
 #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);

+ 10 - 0
arch/sparc64/kernel/smp.c

@@ -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)
 	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_*()

+ 28 - 1
arch/sparc64/mm/ultra.S

@@ -1,7 +1,7 @@
 /*
 /*
  * 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 @@
 #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:
 	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

+ 1 - 0
drivers/char/sysrq.c

@@ -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 */
 };
 };

+ 21 - 0
include/asm-sparc64/ptrace.h

@@ -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 *);
 #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

+ 4 - 1
include/asm-sparc64/smp.h

@@ -1,6 +1,6 @@
 /* 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);
 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);
 
 
 #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) */