/*
 * Copyright (c) 2007, 2008 University of Tsukuba
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the University of Tsukuba nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 2010-2012 Yuichi Watanabe
 */

#include <core/cpu.h>
#include <core/initfunc.h>
#include <core/msg.h>
#include <core/printf.h>
#include <core/spinlock.h>
#include <core/stdarg.h>
#include <core/string.h>
#include "callrealmode.h"
#include "current.h"
#include "int.h"
#include "keyboard.h"
#include "mm.h"
#include "panic.h"
#include "pcpu.h"
#include "process.h"
#include <core/sleep.h>
#include "smp.h"
#include "tty.h"
/* #include "vramwrite.h" */
#include <core/types.h>

#define PANICMSG_BUF_SIZE 256

static spinlock_t panic_lock = SPINLOCK_INITIALIZER;
static char panicmsg[PANICMSG_BUF_SIZE] = "";
static int paniccpu = -1;
static bool do_wakeup = false;

void __attribute__ ((noreturn))
freeze(void)
{
	for (;;) {
		asm_cli_and_hlt ();
	}
}

/* stack */
/* +4/+8 caller */
/*  0    ebp */
/*    ... */
void
backtrace (void)
{
	ulong rbp, nextrbp, *p;
#ifdef __x86_64__
	register ulong start_rbp asm ("%rbp");
#else
	register ulong start_rbp asm ("%ebp");
#endif
	printf ("backtrace: \n");
	for (rbp = start_rbp; ; rbp = nextrbp) {
		p = (ulong *)rbp;
		printf ("    %p: 0x%lx\n", &p[1] , p[1]);
		nextrbp = p[0];
		if (nextrbp == 0)
			break;
		if (rbp > nextrbp
		    || nextrbp - rbp > 1 * 1024 * 1024) {
			printf ("    A next rbp is bad. 0x%lx\n", nextrbp);
			break;
		}
	}
}

static void
dump_general_regs (ulong r[16])
{
	struct {
		char *format;
		enum general_reg reg;
	} *p, data[] = {
		{ "RAX %08lX    ", GENERAL_REG_RAX },
		{ "RCX %08lX    ", GENERAL_REG_RCX },
		{ "RDX %08lX    ", GENERAL_REG_RDX },
		{ "RBX %08lX\n",   GENERAL_REG_RBX },
		{ "RSP %08lX    ", GENERAL_REG_RSP },
		{ "RBP %08lX    ", GENERAL_REG_RBP },
		{ "RSI %08lX    ", GENERAL_REG_RSI },
		{ "RDI %08lX\n",   GENERAL_REG_RDI },
		{ "R8  %08lX    ", GENERAL_REG_R8 },
		{ "R9  %08lX    ", GENERAL_REG_R9 },
		{ "R10 %08lX    ", GENERAL_REG_R10 },
		{ "R11 %08lX\n",   GENERAL_REG_R11 },
		{ "R12 %08lX    ", GENERAL_REG_R12 },
		{ "R13 %08lX    ", GENERAL_REG_R13 },
		{ "R14 %08lX    ", GENERAL_REG_R14 },
		{ "R15 %08lX\n",   GENERAL_REG_R15 },
		{ NULL, 0 },
	};

	for (p = data; p->format; p++)
		printf (p->format, r[p->reg]);
}

static void
dump_control_regs (ulong r[16])
{
	struct {
		char *format;
		enum control_reg reg;
	} *p, data[] = {
		{ "CR0 %08lX    ", CONTROL_REG_CR0 },
		{ "CR2 %08lX    ", CONTROL_REG_CR2 },
		{ "CR3 %08lX    ", CONTROL_REG_CR3 },
		{ "CR4 %08lX\n", CONTROL_REG_CR4 },
		{ NULL, 0 },
	};

	for (p = data; p->format; p++)
		printf (p->format, r[p->reg]);
}

static void
dump_vmm_general_regs (void)
{
	ulong r[16];

#ifdef __x86_64__
	asm volatile ("mov %%rax,%0" : "=m" (r[GENERAL_REG_RAX]));
	asm volatile ("mov %%rcx,%0" : "=m" (r[GENERAL_REG_RCX]));
	asm volatile ("mov %%rdx,%0" : "=m" (r[GENERAL_REG_RDX]));
	asm volatile ("mov %%rbx,%0" : "=m" (r[GENERAL_REG_RBX]));
	asm volatile ("mov %%rsp,%0" : "=m" (r[GENERAL_REG_RSP]));
	asm volatile ("mov %%rbp,%0" : "=m" (r[GENERAL_REG_RBP]));
	asm volatile ("mov %%rsi,%0" : "=m" (r[GENERAL_REG_RSI]));
	asm volatile ("mov %%rdi,%0" : "=m" (r[GENERAL_REG_RDI]));
	asm volatile ("mov %%r8,%0" : "=m" (r[GENERAL_REG_R8]));
	asm volatile ("mov %%r9,%0" : "=m" (r[GENERAL_REG_R9]));
	asm volatile ("mov %%r10,%0" : "=m" (r[GENERAL_REG_R10]));
	asm volatile ("mov %%r11,%0" : "=m" (r[GENERAL_REG_R11]));
	asm volatile ("mov %%r12,%0" : "=m" (r[GENERAL_REG_R12]));
	asm volatile ("mov %%r13,%0" : "=m" (r[GENERAL_REG_R13]));
	asm volatile ("mov %%r14,%0" : "=m" (r[GENERAL_REG_R14]));
	asm volatile ("mov %%r15,%0" : "=m" (r[GENERAL_REG_R15]));
#else
	asm volatile ("mov %%eax,%0" : "=m" (r[GENERAL_REG_RAX]));
	asm volatile ("mov %%ecx,%0" : "=m" (r[GENERAL_REG_RCX]));
	asm volatile ("mov %%edx,%0" : "=m" (r[GENERAL_REG_RDX]));
	asm volatile ("mov %%ebx,%0" : "=m" (r[GENERAL_REG_RBX]));
	asm volatile ("mov %%esp,%0" : "=m" (r[GENERAL_REG_RSP]));
	asm volatile ("mov %%ebp,%0" : "=m" (r[GENERAL_REG_RBP]));
	asm volatile ("mov %%esi,%0" : "=m" (r[GENERAL_REG_RSI]));
	asm volatile ("mov %%edi,%0" : "=m" (r[GENERAL_REG_RDI]));
	r[GENERAL_REG_R8] = 0;
	r[GENERAL_REG_R9] = 0;
	r[GENERAL_REG_R10] = 0;
	r[GENERAL_REG_R11] = 0;
	r[GENERAL_REG_R12] = 0;
	r[GENERAL_REG_R13] = 0;
	r[GENERAL_REG_R14] = 0;
	r[GENERAL_REG_R15] = 0;
#endif
	dump_general_regs (r);
}

static void
dump_vmm_control_regs (void)
{
	ulong r[16];

	asm_rdcr0 (&r[CONTROL_REG_CR0]);
	asm_rdcr2 (&r[CONTROL_REG_CR2]);
	asm_rdcr3 (&r[CONTROL_REG_CR3]);
	asm_rdcr4 (&r[CONTROL_REG_CR4]);
	dump_control_regs (r);
}

static void
dump_vmm_other_regs (void)
{
	ulong tmp, tmp2;

	asm_rdrflags (&tmp);
	printf ("RFLAGS %08lX  ", tmp);
	asm_rdgdtr (&tmp, &tmp2);
	printf ("GDTR %08lX+%08lX  ", tmp, tmp2);
	asm_rdidtr (&tmp, &tmp2);
	printf ("IDTR %08lX+%08lX\n", tmp, tmp2);
}

static void
dump_vm_general_regs (void)
{
	ulong r[16];

	if (!current->vmctl.read_general_reg)
		return;
	current->vmctl.read_general_reg (GENERAL_REG_RAX, &r[GENERAL_REG_RAX]);
	current->vmctl.read_general_reg (GENERAL_REG_RCX, &r[GENERAL_REG_RCX]);
	current->vmctl.read_general_reg (GENERAL_REG_RDX, &r[GENERAL_REG_RDX]);
	current->vmctl.read_general_reg (GENERAL_REG_RBX, &r[GENERAL_REG_RBX]);
	current->vmctl.read_general_reg (GENERAL_REG_RSP, &r[GENERAL_REG_RSP]);
	current->vmctl.read_general_reg (GENERAL_REG_RBP, &r[GENERAL_REG_RBP]);
	current->vmctl.read_general_reg (GENERAL_REG_RSI, &r[GENERAL_REG_RSI]);
	current->vmctl.read_general_reg (GENERAL_REG_RDI, &r[GENERAL_REG_RDI]);
	current->vmctl.read_general_reg (GENERAL_REG_R8,  &r[GENERAL_REG_R8]);
	current->vmctl.read_general_reg (GENERAL_REG_R9,  &r[GENERAL_REG_R9]);
	current->vmctl.read_general_reg (GENERAL_REG_R10, &r[GENERAL_REG_R10]);
	current->vmctl.read_general_reg (GENERAL_REG_R11, &r[GENERAL_REG_R11]);
	current->vmctl.read_general_reg (GENERAL_REG_R12, &r[GENERAL_REG_R12]);
	current->vmctl.read_general_reg (GENERAL_REG_R13, &r[GENERAL_REG_R13]);
	current->vmctl.read_general_reg (GENERAL_REG_R14, &r[GENERAL_REG_R14]);
	current->vmctl.read_general_reg (GENERAL_REG_R15, &r[GENERAL_REG_R15]);
	dump_general_regs (r);
}

static void
dump_vm_control_regs (void)
{
	ulong r[16];

	if (!current->vmctl.read_control_reg)
		return;
	current->vmctl.read_control_reg (CONTROL_REG_CR0, &r[CONTROL_REG_CR0]);
	current->vmctl.read_control_reg (CONTROL_REG_CR2, &r[CONTROL_REG_CR2]);
	current->vmctl.read_control_reg (CONTROL_REG_CR3, &r[CONTROL_REG_CR3]);
	current->vmctl.read_control_reg (CONTROL_REG_CR4, &r[CONTROL_REG_CR4]);
	dump_control_regs (r);
}

static void
dump_vm_sregs (void)
{
	struct {
		char *format;
		enum sreg reg;
	} *p, data[] = {
		{ "ES %08lX ", SREG_ES },
		{ "CS %08lX ", SREG_CS },
		{ "SS %08lX ", SREG_SS },
		{ "DS %08lX ", SREG_DS },
		{ "FS %08lX ", SREG_FS },
		{ "GS %08lX\n", SREG_GS },
		{ NULL, 0 },
	};
	ulong tmp;
	u16 tmp16;

	if (!current->vmctl.read_sreg_sel)
		return;
	if (!current->vmctl.read_sreg_acr)
		return;
	if (!current->vmctl.read_sreg_base)
		return;
	if (!current->vmctl.read_sreg_limit)
		return;
	printf ("ACR   ");
	for (p = data; p->format; p++) {
		current->vmctl.read_sreg_acr (p->reg, &tmp);
		printf (p->format, tmp);
	}
	printf ("LIMIT ");
	for (p = data; p->format; p++) {
		current->vmctl.read_sreg_limit (p->reg, &tmp);
		printf (p->format, tmp);
	}
	printf ("BASE  ");
	for (p = data; p->format; p++) {
		current->vmctl.read_sreg_base (p->reg, &tmp);
		printf (p->format, tmp);
	}
	printf ("SEL   ");
	for (p = data; p->format; p++) {
		current->vmctl.read_sreg_sel (p->reg, &tmp16);
		tmp = tmp16;
		printf (p->format, tmp);
	}
}

static void
dump_vm_other_regs (void)
{
	ulong tmp, tmp2;
	u64 tmp3;

	if (current->vmctl.read_ip) {
		current->vmctl.read_ip (&tmp);
		printf ("RIP %08lX  ", tmp);
	}
	if (current->vmctl.read_flags) {
		current->vmctl.read_flags (&tmp);
		printf ("RFLAGS %08lX  ", tmp);
	}
	if (current->vmctl.read_gdtr) {
		current->vmctl.read_gdtr (&tmp, &tmp2);
		printf ("GDTR %08lX+%08lX  ", tmp, tmp2);
	}
	if (current->vmctl.read_idtr) {
		current->vmctl.read_idtr (&tmp, &tmp2);
		printf ("IDTR %08lX+%08lX", tmp, tmp2);
	}
	printf ("\n");
	if (current->vmctl.read_msr) {
		current->vmctl.read_msr (MSR_IA32_EFER, &tmp3);
		printf ("EFER %08llX\n", tmp3);
	}
	if (current->vmctl.panic_dump)
		current->vmctl.panic_dump ();
}

void
shell(void)
{
	int kbd, dsp, shell;
	shell = newprocess ("shell");
	if (shell == -1) {
		return;
	}
	kbd = msgopen ("ttyin");
	dsp = msgopen ("ttyout");
	msgsenddesc (shell, kbd);
	msgsenddesc (shell, dsp);
	printf("Starting shell.\n");
	msgsendint (shell, 0);
}

static void __attribute__ ((noreturn))
vpanic(struct int_stack *stack, const char *format, va_list ap)
{
	volatile static bool wakeuped = false;
	volatile static bool shell_started = false;
	volatile static int paniccpu_count = 0;

	if (currentcpu_available()) {
		if (currentcpu->panic) {
			printf("The second panic on CPU%d: ", get_cpu_id());
			vprintf(format, ap);
			printf("\n");
			freeze();
		};
		currentcpu->panic = true;
	}


	spinlock_lock (&panic_lock);
	paniccpu_count++;
	if (paniccpu == -1) {
		/* Save the first panic message. */
		vsnprintf(panicmsg, PANICMSG_BUF_SIZE, format, ap);
		paniccpu = get_cpu_id();
		printf("CPU%d panic : %s\n", get_cpu_id(), panicmsg);
	} else {
		printf("CPU%d panic: ", get_cpu_id());
		vprintf(format, ap);
		printf("\n");
	}

	if (stack) {
		int_dump_stack(stack);
	}

	if (currentcpu_available ()) {
		printf ("VMM state of CPU%d ------------------------------\n",
			get_cpu_id ());
		dump_vmm_general_regs ();
		dump_vmm_control_regs ();
		dump_vmm_other_regs ();
		backtrace();
		printf ("------------------------------------------------\n");
	}
	if (currentcpu_available () && current) {
		printf ("Guest state of CPU%d ----------------------------\n",
			get_cpu_id ());
		dump_vm_general_regs ();
		dump_vm_control_regs ();
		dump_vm_sregs ();
		dump_vm_other_regs ();
		printf ("------------------------------------------------\n");
	}
	if (do_wakeup && !wakeuped) {
		printf("Sending NMI to other CPUs\n");
		wakeuped = true;
		panic_wakeup_all ();
	}
	spinlock_unlock (&panic_lock);

	if (!currentcpu_available() || get_cpu_id() != 0) {
		freeze ();
	}

	spinlock_lock (&panic_lock);
	if (wakeuped) {
		spinlock_unlock (&panic_lock);
		for (;;) {
			spinlock_lock (&panic_lock);
			if (paniccpu_count >= get_number_of_processors()) {
				spinlock_unlock (&panic_lock);
				break;
			}
			spinlock_unlock (&panic_lock);
			cpu_relax();
		}
	} else {
		spinlock_unlock (&panic_lock);
	}

/* 	/\* Change vidwo mode to output message. *\/ */
/* 	vramwrite_reenable(); */

	printf ("The first panic occured on CPU%d : %s\n", paniccpu, panicmsg);

	if (!currentcpu_available()) {
		freeze ();
	}

	if (!shell_started) {
		shell_started = true;
		keyboard_reset ();
		usleep (250000);
		setkbdled (LED_SCROLLLOCK_BIT |
			   LED_CAPSLOCK_BIT);
		ttylog_stop ();
		shell();
	}
	freeze ();
}

void __attribute__ ((noreturn))
panic_int(struct int_stack *stack, const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	vpanic(stack, format, ap);
}

void __attribute__ ((noreturn))
panic(char *format, ...)
{
	va_list ap;
	va_start (ap, format);
	vpanic(NULL, format, ap);
}

static int
panic_msghandler (int m, int c)
{
	if (m == 0)
		panic("Panic requested by user");
	return 0;
}

static void
panic_init_msg (void)
{
	msgregister ("panic", panic_msghandler);
}

static void
panic_init_pcpu (void)
{
	sync_all_processors();
	do_wakeup = true;
}

INITFUNC ("msg0", panic_init_msg);
INITFUNC ("pcpu0", panic_init_pcpu);
