/*
 * Architecture specific (ia64) functions for Linux crash dumps.
 *
 * Created by: Matt Robinson (yakker@sgi.com)
 * Contributions from SGI, IBM, and others.
 *
 * 2.4  kernel modifications by:  Matt D. Robinson (yakker@alacritech.com)
 * ia64 kernel modifications by: Piet Delaney (piet@www.piet.net)
 *
 * Copyright (C) 2001 - 2002 Matt D. Robinson (yakker@alacritech.com)
 * Copyright (C) 2002 Silicon Graphics, Inc. All rights reserved.
 * Copyright (C) 2002 Free Software Foundation, 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>

extern unsigned long irq_affinity[];

/* static variables */
static dump_header_asm_t dump_header_asm;
static unsigned long saved_affinity[NR_IRQS];

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;
}

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

	if (tsk && 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 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;
}

#else
#define save_other_cpu_states()
#endif

/*
 * 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();

	/* flush the dirty registers to backing store */
	asm("flushrs");

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

	/* Save the pointer to the pt_regs struct */
	dump_header_asm.dha_pt_regs = regs;

	save_this_cpu_state(cpu, regs, current);

	save_other_cpu_states();

	/* return success */
	return (1);
}


#define dim(x) (sizeof(x)/sizeof(*(x)))

void
__init_mem_banks(void)
{
	void *p;
	int i = 0;
	void *efi_map_start = __va(ia64_boot_param->efi_memmap);
	void *efi_map_end = efi_map_start + ia64_boot_param->efi_memmap_size;
	u64 efi_desc_size = ia64_boot_param->efi_memdesc_size;

	memset (dump_mbank, 0, sizeof dump_mbank);

	for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) {
		efi_memory_desc_t *md = p;
		u64 start = md->phys_addr;
		u64 end = start + (md->num_pages << DUMP_EF_PAGE_SHIFT) - 1;
		int type;

		if (i >= dim(dump_mbank)) {
			break;
		}

		/*
		 * NB: The attribute EFI_MEMORY_UC means the memory bank can support Uncached access,
		 *      not that the memory us curently running with Uncached access.
		 */
		if ( (md->attribute & (EFI_MEMORY_WB | EFI_MEMORY_WT)) == 0 ) {
			continue;
		}
		switch(md->type) {
		case EFI_UNUSABLE_MEMORY:		/* Ignore it */
			continue;

		case EFI_LOADER_CODE:
		case EFI_LOADER_DATA:
		case EFI_BOOT_SERVICES_CODE:
		case EFI_BOOT_SERVICES_DATA:
		case EFI_CONVENTIONAL_MEMORY:
			type = DUMP_MBANK_TYPE_CONVENTIONAL_MEMORY;
			break;
		
		default:
			type = DUMP_MBANK_TYPE_OTHER;
			break;

		}
		dump_mbank[ i ].type = type;
		dump_mbank[ i ].start = start;
		dump_mbank[ i ].end = end;
		i++;
	}
	dump_mbanks = i;
	return;
}

/*
 * 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)
{
	__init_mem_banks();		/* Initialize Memory Banks */
	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();

	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();

	return;
}

#ifdef CONFIG_SMP
/*
 * 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.
 * 
 * NB: Need to be expanded to multiple nodes.
 */
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]);
		}
	}
}
#endif


/*
 * Name: __dump_silence_system()
 * Func: Do an architecture-specific silencing of the system.
 */
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 (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 (0);
}


int __dump_memcpy_mc_expected = 0;		/* Doesn't help yet */

/*
 * An ia64 version of memcpy() that trys to avoid machine checks.
 *
 * NB: 
 * 	By itself __dump_memcpy_mc_expected() ins't providing any
 *	protection against Machine Checks. We are looking into the
 *	possability of adding code to the arch/ia64/kernel/mca.c fuction
 *	ia64_mca_ucmc_handler() to restore state so that a IA64_MCA_CORRECTED
 *	can be returned to the firmware. Curently it always returns 
 *	IA64_MCA_COLD_BOOT and reboots the machine.
 */
void * __dump_memcpy(void * dest, const void *src, size_t count)
{
	void *vp;

	if (__dump_memcpy_mc_expected) {
		ia64_pal_mc_expected((u64) 1, 0);
	}

	vp = memcpy(dest, src, count);

	if (__dump_memcpy_mc_expected) {
		ia64_pal_mc_expected((u64) 0, 0);
	}
	return(vp);
}
