/*
 * Early initialization code for riscv
 *
 * Copyright 2015 Google Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of
 * the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
 * GNU General Public License for more details.
 */

#include <arch/encoding.h>
#include <arch/exception.h>
#include <console/console.h>
#include <vm.h>
#include <mcall.h>
#include <sbi.h>

static const char *const exception_names[] = {
	"Instruction address misaligned",
	"Instruction access fault",
	"Illegal instruction",
	"Breakpoint",
	"Load address misaligned",
	"Load access fault",
	"Store address misaligned",
	"Store access fault",
	"Environment call from U-mode",
	"Environment call from S-mode",
	"Reserved (10)",
	"Environment call from M-mode",
	"Instruction page fault",
	"Load page fault",
	"Reserved (14)",
	"Store page fault",
};

static const char *mstatus_to_previous_mode(uintptr_t ms)
{
	switch (ms & MSTATUS_MPP) {
		case 0x00000000: return "user";
		case 0x00000800: return "supervisor";
		case 0x00001000: return "hypervisor";
		case 0x00001800: return "machine";
	}

	return "unknown";
}

static void print_trap_information(const trapframe *tf)
{
	const char *previous_mode;
	bool mprv = !!(tf->status & MSTATUS_MPRV);
	int hart_id = read_csr(mhartid);

	/* Leave some space around the trap message */
	printk(BIOS_DEBUG, "\n");

	if (tf->cause < ARRAY_SIZE(exception_names))
		printk(BIOS_DEBUG, "Exception:          %s\n",
				exception_names[tf->cause]);
	else
		printk(BIOS_DEBUG, "Trap:               Unknown cause %p\n",
				(void *)tf->cause);

	previous_mode = mstatus_to_previous_mode(read_csr(mstatus));
	printk(BIOS_DEBUG, "Hart ID:            %d\n", hart_id);
	printk(BIOS_DEBUG, "Previous mode:      %s%s\n",
			previous_mode, mprv? " (MPRV)":"");
	printk(BIOS_DEBUG, "Bad instruction pc: %p\n", (void *)tf->epc);
	printk(BIOS_DEBUG, "Bad address:        %p\n", (void *)tf->badvaddr);
	printk(BIOS_DEBUG, "Stored ra:          %p\n", (void*) tf->gpr[1]);
	printk(BIOS_DEBUG, "Stored sp:          %p\n", (void*) tf->gpr[2]);
}

static void interrupt_handler(trapframe *tf)
{
	uint64_t cause = tf->cause & ~0x8000000000000000ULL;

	switch (cause) {
	case IRQ_M_TIMER:
		/*
		 * Set interrupt pending for supervisor mode and disable timer
		 * interrupt in machine mode.
		 * To receive another timer interrupt just set timecmp and
		 * enable machine mode timer interrupt again.
		 */

		clear_csr(mie, MIP_MTIP);
		set_csr(mip, MIP_STIP);

		break;
	case IRQ_M_SOFT:
		if (HLS()->ipi_pending & IPI_SOFT) {
			set_csr(mip, MIP_SSIP);
		} else if (HLS()->ipi_pending & IPI_FENCE_I) {
			asm volatile("fence.i");
		} else if (HLS()->ipi_pending & IPI_SFENCE_VMA) {
			asm volatile("sfence.vma");
		} else if (HLS()->ipi_pending & IPI_SFENCE_VMA_ASID) {
			asm volatile("sfence.vma");
		} else if (HLS()->ipi_pending & IPI_SHUTDOWN) {
			while (HLS()->ipi_pending & IPI_SHUTDOWN)
				asm volatile("wfi");
		}
		break;
	default:
		printk(BIOS_EMERG, "======================================\n");
		printk(BIOS_EMERG, "coreboot: Unknown machine interrupt: 0x%llx\n",
		       cause);
		printk(BIOS_EMERG, "======================================\n");
		print_trap_information(tf);
		break;
	}
}
void trap_handler(trapframe *tf)
{
	write_csr(mscratch, tf);
	if (tf->cause & 0x8000000000000000ULL) {
		interrupt_handler(tf);
		return;
	}

	switch (tf->cause) {
		case CAUSE_MISALIGNED_FETCH:
		case CAUSE_FETCH_ACCESS:
		case CAUSE_ILLEGAL_INSTRUCTION:
		case CAUSE_BREAKPOINT:
		case CAUSE_LOAD_ACCESS:
		case CAUSE_STORE_ACCESS:
		case CAUSE_USER_ECALL:
		case CAUSE_HYPERVISOR_ECALL:
		case CAUSE_MACHINE_ECALL:
			print_trap_information(tf);
			break;
		case CAUSE_SUPERVISOR_ECALL:
			handle_sbi(tf);
			return;
		case CAUSE_MISALIGNED_LOAD:
		case CAUSE_MISALIGNED_STORE:
			print_trap_information(tf);
			handle_misaligned(tf);
			return;
		default:
			printk(BIOS_EMERG, "================================\n");
			printk(BIOS_EMERG, "coreboot: can not handle a trap:\n");
			printk(BIOS_EMERG, "================================\n");
			print_trap_information(tf);
			break;
	}

	die("Can't recover from trap. Halting.\n");
}

/* This function used to redirect trap to s-mode. */
void redirect_trap(void)
{
	write_csr(sbadaddr, read_csr(mbadaddr));
	write_csr(sepc, read_csr(mepc));
	write_csr(scause, read_csr(mcause));
	write_csr(mepc, read_csr(stvec));

	uintptr_t status = read_csr(mstatus);
	uintptr_t mpp = EXTRACT_FIELD(status, MSTATUS_MPP);
	status = INSERT_FIELD(status, MSTATUS_MPP, 1);
	status = INSERT_FIELD(status, MSTATUS_SPP, mpp & 1);
	write_csr(mstatus, status);
}