/*
 * Architecture specific (i386) functions for Linux crash dumps.
 *
 * Created by: Matt Robinson (yakker@sgi.com)
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 *
 * 2.3 kernel modifications by: Matt D. Robinson (yakker@turbolinux.com)
 * Copyright 2000 TurboLinux, Inc.  All rights reserved.
 * 
 * This code is released under version 2 of the GNU GPL.
 */

/*
 * The hooks for dumping the kernel virtual memory to disk are in this
 * file.  Any time a modification is made to the virtual memory mechanism,
 * these routines must be changed to use the new mechanisms.
 */
#include <linux/init.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
#include <linux/dump.h>
#include <linux/mm.h>
#include <asm/processor.h>
#include <asm/hardirq.h>
#include <linux/irq.h>

static int alloc_dha_stack(void)
{
	int i;
	void *ptr;
	
	if (dump_header_asm.dha_stack[0])
		return 0;

       	ptr = vmalloc(THREAD_SIZE * smp_num_cpus);
	if (!ptr) {
		printk("vmalloc for dha_stacks failed\n");
		return -ENOMEM;
	}

	for (i = 0; i < smp_num_cpus; i++) {
		dump_header_asm.dha_stack[i] = (void *)((unsigned long)ptr + (i * THREAD_SIZE));
	}
	return 0;
}

static int free_dha_stack(void) 
{
	if (dump_header_asm.dha_stack[0])
		vfree(dump_header_asm.dha_stack[0]);	
	return 0;
}

/* In case of panic dumps, we collects regs on entry to panic.
 * so, we shouldn't 'fix' ssesp here again. But it is hard to
 * tell just looking at regs whether ssesp need fixing. We make
 * this decision by looking at xss in regs. If we have better
 * means to determine that ssesp are valid (by some flag which
 * tells that we are here due to panic dump), then we can use
 * that instead of this kludge.
 */
static inline void 
fix_ssesp(struct pt_regs *regs, int cpu)
{
	if (!user_mode(regs)) {
		if ((cpu == dump_header_asm.dha_dumping_cpu) &&
			regs->xss == __KERNEL_DS) 
			return;
		dump_header_asm.dha_smp_regs[cpu].esp = 
				(unsigned long)&(regs->esp);
		__asm__ __volatile__ ("movw %%ss, %%ax;"
			:"=a"(dump_header_asm.dha_smp_regs[cpu].xss));
	}
}

static void
save_this_cpu_state(int cpu, struct pt_regs *regs, struct task_struct *tsk)
{
	dump_header_asm.dha_smp_regs[cpu] = *regs;
	dump_header_asm.dha_smp_current_task[cpu] = tsk;
	fix_ssesp(regs, cpu);

	if (dump_header_asm.dha_stack[cpu]) {
		memcpy(dump_header_asm.dha_stack[cpu], tsk, THREAD_SIZE);
	}
	return;
}

#ifdef CONFIG_SMP
static int dump_expect_ipi[NR_CPUS];
static atomic_t waiting_for_dump_ipi;
static int wait_for_dump_ipi = 1; /* always wait for ipi to to be handled */
extern void (*dump_trace_ptr)(struct pt_regs *);
static unsigned long saved_affinity[NR_IRQS];

static int
dump_ipi_handler(struct pt_regs *regs) 
{
	int cpu = smp_processor_id();

	if (!dump_expect_ipi[cpu]) {
		return 0;
	}
	
	save_this_cpu_state(cpu, regs, current);

	dump_expect_ipi[cpu] = 0;
	atomic_dec(&waiting_for_dump_ipi);
	return 1;
}

/* save registers on other processors */
void 
save_other_cpu_states(void)
{
	int i;

	if (smp_num_cpus > 1) {
		atomic_set(&waiting_for_dump_ipi, smp_num_cpus-1);
		for (i = 0; i < NR_CPUS; i++)
			dump_expect_ipi[i] = 1;
		
		dump_ipi_function_ptr = dump_ipi_handler;
		dump_send_ipi();
		/* may be we dont need to wait for NMI to be processed. 
		   just write out the header at the end of dumping, if
		   this IPI is not processed untill then, there probably
		   is a problem and we just fail to capture state of 
		   other cpus. */
		if (wait_for_dump_ipi) {
			while(atomic_read(&waiting_for_dump_ipi))
				barrier();
			dump_ipi_function_ptr = NULL;
		}
	}
	return;
}

/* located in arch/i386/kernel/traps.c */
extern void show_this_cpu_state(int cpu, struct pt_regs * regs, struct task_struct *tsk);

void 
show_cpu_state(struct pt_regs * regs)
{
	int i;
	__dump_configure_header(regs);	
	for (i = 0; i < smp_num_cpus; i++) {
		show_this_cpu_state(i, &dump_header_asm.dha_smp_regs[i], 
			dump_header_asm.dha_stack[i]);
	}
	return;
}

/*
 * Non dumping cpus will spin here. If a cpu is handling an irq when ipi is
 * received, we let go of it here while making sure that it hits schedule
 * on the way up and make it spin there instead.
 */
static void
__dump_spin(void *arg)
{
	if (in_irq()) {
		current->need_resched = 1;
	} else {
		while (dump_in_progress) ;
	}
	return;
}

/*
 * Routine to save the old irq affinities and change affinities of all irqs to
 * the dumping cpu.
 */
static void
__dump_set_irq_affinity(void)
{
	int i;
	int cpu = smp_processor_id();

	memcpy(saved_affinity, irq_affinity, NR_IRQS * sizeof(unsigned long));
	for (i = 0; i < NR_IRQS; i++) {
		if (irq_desc[i].handler == NULL)
			continue;
		irq_affinity[i] = 1UL << cpu;
		if (irq_desc[i].handler->set_affinity != NULL)
			irq_desc[i].handler->set_affinity(i, irq_affinity[i]);
	}
}

/*
 * Restore old irq affinities.
 */
static void
__dump_reset_irq_affinity(void)
{
	int i;

	memcpy(irq_affinity, saved_affinity, NR_IRQS * sizeof(unsigned long));
	for (i = 0; i < NR_IRQS; i++) {
		if (irq_desc[i].handler == NULL)
			continue;
		if (irq_desc[i].handler->set_affinity != NULL)
			irq_desc[i].handler->set_affinity(i, saved_affinity[i]);
	}
}

#else /* !CONFIG_SMP */
#define save_other_cpu_states()
#endif /* !CONFIG_SMP */

/*
 * Name: __dump_silence_system()
 * Func: Do an architecture-specific silencing of the system.
 *     - Change irq affinities
 *     - Wait for other cpus to come out of irq handling
 *     - Send CALL_FUNCTION_VECTOR ipi to other cpus to put them to spin
 */
unsigned int
__dump_silence_system(unsigned int stage)
{
#ifdef CONFIG_SMP
	if (stage) {	/* Do this after interrupts are enabled */
		/* read the comments above ... */
		__dump_set_irq_affinity();
		synchronize_irq();
		smp_call_function(__dump_spin, NULL, 0, 0);
	}
#endif

	/* return */
	return (0);
}

/*
 * Name: __dump_resume_system()
 * Func: Resume the system state in an architecture-specific way.
 */
unsigned int
__dump_resume_system(unsigned int stage)
{
#ifdef CONFIG_SMP
	/* put the irq affinity tables back */
	__dump_reset_irq_affinity();
#endif

	/* return */
	return (0);
}

/*
 * Name: __dump_configure_header()
 * Func: Configure the dump header with all proper values.
 */
int
__dump_configure_header(struct pt_regs *regs)
{
	int cpu = smp_processor_id();

	dump_header_asm.dha_smp_num_cpus = smp_num_cpus;
	dump_header_asm.dha_dumping_cpu = cpu;

	save_this_cpu_state(cpu, regs, current);

	save_other_cpu_states();

	return (1);
}

/*
 * Name: __dump_init()
 * Func: Initialize the dumping routine process.  This is in case
 *       it's necessary in the future.
 */
void
__dump_init(uint64_t local_memory_start)
{
	/* return */
	return;
}

/*
 * Name: __dump_open()
 * Func: Open the dump device (architecture specific).  This is in
 *       case it's necessary in the future.
 */
void
__dump_open(void)
{
	alloc_dha_stack();
#ifdef CONFIG_SMP	
	dump_trace_ptr = show_cpu_state;
#endif
	/* return */
	return;
}

/*
 * Name: __dump_cleanup()
 * Func: Free any architecture specific data structures. This is called
 *       when the dump module is being removed.
 */
void
__dump_cleanup(void)
{
	free_dha_stack();
#ifdef CONFIG_SMP
	dump_trace_ptr = NULL;
#endif
	/* return */
	return;
}

