/*
 * Copyright (c) 2023-2024, Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <string.h>

#include <bl31/interrupt_mgmt.h>
#include <lib/el3_runtime/context_mgmt.h>
#include <lib/extensions/ras.h>
#include <plat/common/platform.h>
#include <services/sdei.h>
#include <services/spm_mm_svc.h>

#include <nrd_ras.h>

#define CPU_CONTEXT_REG_GPR_ARR_SIZE 32
#define CPU_CONTEXT_REG_EL1_ARR_SIZE 17
#define CPU_CONTEXT_REG_EL2_ARR_SIZE 16
#define CPU_CONTEXT_REG_EL3_ARR_SIZE 10

/*
 * MM Communicate message header GUID to indicate the payload is intended for
 * CPU MM driver.
 */
struct efi_guid cpu_ecc_event_guid = {
	0x2c1b3bfc, 0x42cd, 0x4a66,
	{0xac, 0xd1, 0xa4, 0xd1, 0x63, 0xe9, 0x90, 0xf6}
	};

/*
 * CPU error information data structure communicated as part of MM
 * Communication data payload.
 */
typedef struct {
	uint64_t ErrStatus;
	uint64_t ErrMisc0;
	uint64_t ErrAddr;
	uint64_t SecurityState;
	uint64_t ErrCtxGpr[CPU_CONTEXT_REG_GPR_ARR_SIZE];
	uint64_t ErrCtxEl1Reg[CPU_CONTEXT_REG_EL1_ARR_SIZE];
	uint64_t ErrCtxEl2Reg[CPU_CONTEXT_REG_EL2_ARR_SIZE];
	uint64_t ErrCtxEl3Reg[CPU_CONTEXT_REG_EL3_ARR_SIZE];
} cpu_err_info;

/*
 * Reads the CPU context and error information from the relevant registers and
 * populates the CPU error information data structure.
 */
static void populate_cpu_err_data(cpu_err_info *cpu_info,
					uint64_t security_state)
{
	void *ctx;

	ctx = cm_get_context(security_state);

	cpu_info->ErrStatus = read_erxstatus_el1();
	cpu_info->ErrMisc0 = read_erxmisc0_el1();
	cpu_info->ErrAddr = read_erxaddr_el1();
	cpu_info->SecurityState = security_state;

	/* populate CPU EL1 context information. */
	cpu_info->ErrCtxEl1Reg[0]  = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  elr_el1);
	cpu_info->ErrCtxEl1Reg[1]  = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  esr_el1);
	cpu_info->ErrCtxEl1Reg[2]  = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  far_el1);
	cpu_info->ErrCtxEl1Reg[3]  = read_isr_el1();
	cpu_info->ErrCtxEl1Reg[4]  = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  mair_el1);
	cpu_info->ErrCtxEl1Reg[5]  = read_midr_el1();
	cpu_info->ErrCtxEl1Reg[6]  = read_mpidr_el1();
	cpu_info->ErrCtxEl1Reg[7] = read_ctx_sctlr_el1_reg_errata(ctx);
	cpu_info->ErrCtxEl1Reg[8]  = read_ctx_reg(get_gpregs_ctx(ctx),
						  CTX_GPREG_SP_EL0);
	cpu_info->ErrCtxEl1Reg[9]  = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  sp_el1);
	cpu_info->ErrCtxEl1Reg[10] = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  spsr_el1);
	cpu_info->ErrCtxEl1Reg[11] = read_ctx_tcr_el1_reg_errata(ctx);
	cpu_info->ErrCtxEl1Reg[12] = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  tpidr_el0);
	cpu_info->ErrCtxEl1Reg[13] = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  tpidr_el1);
	cpu_info->ErrCtxEl1Reg[14] = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  tpidrro_el0);
	cpu_info->ErrCtxEl1Reg[15] = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  ttbr0_el1);
	cpu_info->ErrCtxEl1Reg[16] = read_el1_ctx_common(get_el1_sysregs_ctx(ctx),
						  ttbr1_el1);

#if (CTX_INCLUDE_EL2_REGS && IMAGE_BL31)
	cpu_info->ErrCtxEl2Reg[0]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						elr_el2);
	cpu_info->ErrCtxEl2Reg[1]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						esr_el2);
	cpu_info->ErrCtxEl2Reg[2]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						far_el2);
	cpu_info->ErrCtxEl2Reg[3]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						hacr_el2);
	cpu_info->ErrCtxEl2Reg[4]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						hcr_el2);
	cpu_info->ErrCtxEl2Reg[5]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						hpfar_el2);
	cpu_info->ErrCtxEl2Reg[6]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						mair_el2);
	cpu_info->ErrCtxEl2Reg[7]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						sctlr_el2);
	cpu_info->ErrCtxEl2Reg[8]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						sp_el2);
	cpu_info->ErrCtxEl2Reg[9]   = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						spsr_el2);
	cpu_info->ErrCtxEl2Reg[10]  = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						tcr_el2);
	cpu_info->ErrCtxEl2Reg[11]  = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						tpidr_el2);
	cpu_info->ErrCtxEl2Reg[12]  = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						ttbr0_el2);
	cpu_info->ErrCtxEl2Reg[13]  = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						vtcr_el2);
	cpu_info->ErrCtxEl2Reg[14]  = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						vttbr_el2);
	cpu_info->ErrCtxEl2Reg[15]  = read_el2_ctx_common(get_el2_sysregs_ctx(ctx),
						esr_el2);
#endif /* (CTX_INCLUDE_EL2_REGS && IMAGE_BL31) */

	cpu_info->ErrCtxEl3Reg[0]   = read_ctx_reg(get_el3state_ctx(ctx),
						   CTX_ELR_EL3);
	cpu_info->ErrCtxEl3Reg[1]   = read_ctx_reg(get_el3state_ctx(ctx),
						   CTX_ESR_EL3);
	cpu_info->ErrCtxEl3Reg[2]   = read_far_el3();
	cpu_info->ErrCtxEl3Reg[4]   = read_mair_el3();
	cpu_info->ErrCtxEl3Reg[5]   = read_sctlr_el3();
	cpu_info->ErrCtxEl3Reg[6]   = 0; /* sp_el3 */
	cpu_info->ErrCtxEl3Reg[7]   = read_tcr_el3();
	cpu_info->ErrCtxEl3Reg[8]   = read_tpidr_el3();
	cpu_info->ErrCtxEl3Reg[9]   = read_ttbr0_el3();
}

/* CPU RAS interrupt handler */
int nrd_ras_cpu_intr_handler(const struct err_record_info *err_rec,
				int probe_data,
				const struct err_handler_data *const data)
{
	struct nrd_ras_ev_map *ras_map;
	mm_communicate_header_t *header;
	cpu_err_info cpu_info = {0};
	uint64_t clear_status;
	uint32_t intr;
	int ret;

	cm_el1_sysregs_context_save(NON_SECURE);
	intr = data->interrupt;

	INFO("[CPU RAS] CPU intr received = %d on cpu_id = %d\n",
		intr, plat_my_core_pos());

	INFO("[CPU RAS] ERXMISC0_EL1 = 0x%lx\n", read_erxmisc0_el1());
	INFO("[CPU RAS] ERXSTATUS_EL1 = 0x%lx\n", read_erxstatus_el1());
	INFO("[CPU RAS] ERXADDR_EL1 = 0x%lx\n", read_erxaddr_el1());

	/* Populate CPU Error Source Information. */
	populate_cpu_err_data(&cpu_info, get_interrupt_src_ss(data->flags));

	/* Clear the interrupt. */
	clear_status = read_erxstatus_el1();
	write_erxstatus_el1(clear_status);
	plat_ic_end_of_interrupt(intr);

	header = (void *) PLAT_SPM_BUF_BASE;
	memset(header, 0, sizeof(*header));
	memcpy(&header->data, &cpu_info, sizeof(cpu_info));
	header->message_len = sizeof(cpu_info);
	memcpy(&header->header_guid, (void *) &cpu_ecc_event_guid,
		sizeof(struct efi_guid));

	spm_mm_sp_call(MM_COMMUNICATE_AARCH64, (uint64_t)header, 0,
		       plat_my_core_pos());

	/*
	 * Find if this is a RAS interrupt. There must be an event against
	 * this interrupt
	 */
	ras_map = nrd_find_ras_event_map_by_intr(intr);
	if (ras_map == NULL) {
		ERROR("NRD: RAS error info for interrupt id: %d not found\n",
			intr);
		return -1;
	}

	/* Dispatch the event to the SDEI client */
	ret = sdei_dispatch_event(ras_map->sdei_ev_num);
	if (ret != 0) {
		/*
		 * sdei_dispatch_event() may return failing result in some
		 * cases, for example kernel may not have registered a handler
		 * or RAS event may happen early during boot. We restore the NS
		 * context when sdei_dispatch_event() returns failing result.
		 */
		ERROR("SDEI dispatch failed: %d", ret);
		cm_el1_sysregs_context_restore(NON_SECURE);
		cm_set_next_eret_context(NON_SECURE);
	}

	return ret;
}
