#undef DEBUG
#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/reboot.h>
#include <linux/kexec.h>
#include <linux/bootmem.h>
#include <linux/crash_dump.h>
#include <linux/delay.h>
#include <linux/elf.h>
#include <linux/elfcore.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/memblock.h>
#include <asm/processor.h>
#include <asm/machdep.h>
#include <asm/kexec.h>
#include <asm/kdump.h>
#include <asm/prom.h>
#include <asm/firmware.h>
#include <asm/smp.h>
#include <asm/system.h>
#include <asm/setjmp.h>
#ifdef DEBUG
#include <asm/udbg.h>
#define DBG(fmt...) udbg_printf(fmt)
#else
#define DBG(fmt...)
#endif
int crashing_cpu = -1;
static cpumask_t cpus_in_crash = CPU_MASK_NONE;
cpumask_t cpus_in_sr = CPU_MASK_NONE;
#define CRASH_HANDLER_MAX 3
static crash_shutdown_t crash_shutdown_handles[CRASH_HANDLER_MAX+1];
static DEFINE_SPINLOCK(crash_handlers_lock);
#ifdef CONFIG_SMP
static atomic_t enter_on_soft_reset = ATOMIC_INIT(0);
void crash_ipi_callback(struct pt_regs *regs)
{
int cpu = smp_processor_id();
if (!cpu_online(cpu))
return;
hard_irq_disable();
if (!cpumask_test_cpu(cpu, &cpus_in_crash))
crash_save_cpu(regs, cpu);
cpumask_set_cpu(cpu, &cpus_in_crash);
if (cpumask_test_cpu(cpu, &cpus_in_sr)) {
cpumask_clear_cpu(cpu, &cpus_in_sr);
atomic_inc(&enter_on_soft_reset);
}
while (!cpumask_test_cpu(crashing_cpu, &cpus_in_crash))
cpu_relax();
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(1, 1);
#ifdef CONFIG_PPC64
kexec_smp_wait();
#else
for (;;);
#endif
}
static void crash_soft_reset_check(int cpu)
{
unsigned int ncpus = num_online_cpus() - 1;
cpumask_clear_cpu(cpu, &cpus_in_sr);
while (atomic_read(&enter_on_soft_reset) != ncpus)
cpu_relax();
}
static void crash_kexec_prepare_cpus(int cpu)
{
unsigned int msecs;
unsigned int ncpus = num_online_cpus() - 1;
crash_send_ipi(crash_ipi_callback);
smp_wmb();
printk(KERN_EMERG "Sending IPI to other cpus...\n");
msecs = 10000;
while ((cpumask_weight(&cpus_in_crash) < ncpus) && (--msecs > 0)) {
cpu_relax();
mdelay(1);
}
if (cpumask_weight(&cpus_in_crash) < ncpus) {
printk(KERN_EMERG "done waiting: %d cpu(s) not responding\n",
ncpus - cpumask_weight(&cpus_in_crash));
printk(KERN_EMERG "Activate soft-reset to stop other cpu(s)\n");
cpumask_clear(&cpus_in_sr);
atomic_set(&enter_on_soft_reset, 0);
while (cpumask_weight(&cpus_in_crash) < ncpus)
cpu_relax();
}
if (cpumask_test_cpu(cpu, &cpus_in_sr))
crash_soft_reset_check(cpu);
}
void crash_kexec_secondary(struct pt_regs *regs)
{
int cpu = smp_processor_id();
unsigned long flags;
int msecs = 5;
local_irq_save(flags);
while (crashing_cpu < 0) {
if (--msecs < 0) {
cpumask_clear_cpu(cpu, &cpus_in_sr);
local_irq_restore(flags);
return;
}
mdelay(1);
cpu_relax();
}
if (cpu == crashing_cpu) {
crash_soft_reset_check(cpu);
cpumask_set_cpu(crashing_cpu, &cpus_in_crash);
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(1, 0);
machine_kexec(kexec_crash_image);
}
crash_ipi_callback(regs);
}
#else
static void crash_kexec_prepare_cpus(int cpu)
{
#ifdef CONFIG_PPC64
smp_release_cpus();
#else
#endif
}
void crash_kexec_secondary(struct pt_regs *regs)
{
cpumask_clear(&cpus_in_sr);
}
#endif
#if defined(CONFIG_SMP) && defined(CONFIG_PPC_STD_MMU_64)
static void crash_kexec_wait_realmode(int cpu)
{
unsigned int msecs;
int i;
msecs = 10000;
for (i=0; i < nr_cpu_ids && msecs > 0; i++) {
if (i == cpu)
continue;
while (paca[i].kexec_state < KEXEC_STATE_REAL_MODE) {
barrier();
if (!cpu_possible(i)) {
break;
}
if (!cpu_online(i)) {
break;
}
msecs--;
mdelay(1);
}
}
mb();
}
#else
static inline void crash_kexec_wait_realmode(int cpu) {}
#endif
int crash_shutdown_register(crash_shutdown_t handler)
{
unsigned int i, rc;
spin_lock(&crash_handlers_lock);
for (i = 0 ; i < CRASH_HANDLER_MAX; i++)
if (!crash_shutdown_handles[i]) {
crash_shutdown_handles[i] = handler;
rc = 0;
break;
}
if (i == CRASH_HANDLER_MAX) {
printk(KERN_ERR "Crash shutdown handles full, "
"not registered.\n");
rc = 1;
}
spin_unlock(&crash_handlers_lock);
return rc;
}
EXPORT_SYMBOL(crash_shutdown_register);
int crash_shutdown_unregister(crash_shutdown_t handler)
{
unsigned int i, rc;
spin_lock(&crash_handlers_lock);
for (i = 0 ; i < CRASH_HANDLER_MAX; i++)
if (crash_shutdown_handles[i] == handler)
break;
if (i == CRASH_HANDLER_MAX) {
printk(KERN_ERR "Crash shutdown handle not found\n");
rc = 1;
} else {
for (; crash_shutdown_handles[i]; i++)
crash_shutdown_handles[i] =
crash_shutdown_handles[i+1];
rc = 0;
}
spin_unlock(&crash_handlers_lock);
return rc;
}
EXPORT_SYMBOL(crash_shutdown_unregister);
static unsigned long crash_shutdown_buf[JMP_BUF_LEN];
static int crash_shutdown_cpu = -1;
static int handle_fault(struct pt_regs *regs)
{
if (crash_shutdown_cpu == smp_processor_id())
longjmp(crash_shutdown_buf, 1);
return 0;
}
void default_machine_crash_shutdown(struct pt_regs *regs)
{
unsigned int i;
int (*old_handler)(struct pt_regs *regs);
hard_irq_disable();
crashing_cpu = smp_processor_id();
crash_save_cpu(regs, crashing_cpu);
crash_kexec_prepare_cpus(crashing_cpu);
cpumask_set_cpu(crashing_cpu, &cpus_in_crash);
crash_kexec_wait_realmode(crashing_cpu);
machine_kexec_mask_interrupts();
old_handler = __debugger_fault_handler;
__debugger_fault_handler = handle_fault;
crash_shutdown_cpu = smp_processor_id();
for (i = 0; crash_shutdown_handles[i]; i++) {
if (setjmp(crash_shutdown_buf) == 0) {
asm volatile("sync; isync");
crash_shutdown_handles[i]();
asm volatile("sync; isync");
}
}
crash_shutdown_cpu = -1;
__debugger_fault_handler = old_handler;
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(1, 0);
}