/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Early initialization code for riscv
 */

#include <arch/encoding.h>
#include <arch/exception.h>
#include <console/console.h>
#include <vm.h>
#include <mcall.h>
#include <sbi.h>
#include <types.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 struct 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(struct 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(struct trapframe *tf)
{
	if (tf->cause & 0x8000000000000000ULL) {
		interrupt_handler(tf);
		return;
	}

	switch (tf->cause) {
	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_FETCH:
	case CAUSE_MISALIGNED_LOAD:
	case CAUSE_MISALIGNED_STORE:
		print_trap_information(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(stval, read_csr(mtval));
	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);
}
