// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright 2019 Broadcom.
 */

#include <drivers/bcm/bnxt.h>
#include <io.h>
#include <kernel/pseudo_ta.h>
#include <mm/core_memprot.h>
#include <mm/core_mmu.h>
#include <string.h>
#include <trace.h>

#define ELOG_SERVICE_UUID \
		{ 0x6272636D, 0x2019, 0x0701, \
		{ 0x42, 0x43, 0x4D, 0x5F, 0x45, 0x4C, 0x4F, 0x47 } }

#define ELOG_TA_NAME			"pta_bcm_elog.ta"

#define BCM_NITRO_FW_LOAD_ADDR			0x8ae00000
#define BCM_NITRO_CRASH_DUMP_BASE_ADDR		0x8b000000

/* Default ELOG buffer size 1MB */
#define DEFAULT_ELOG_BUFFER_SIZE		0x100000U

/*
 * Get Error log memory dump
 *
 * [out]    memref[0]:    Destination
 * [in]     value[1].a:   Offset
 */
#define PTA_BCM_ELOG_CMD_GET_ELOG_MEM		1

/*
 * Get nitro crash_dump memory
 *
 * [out]    memref[0]:    Destination
 * [in]     value[1].a:   Offset
 */
#define PTA_BCM_ELOG_CMD_GET_NITRO_CRASH_DUMP	2

/*
 * Load nitro firmware memory
 *
 * [in]     memref[0]:    Nitro f/w image data
 * [in]     value[1].a:   Offset for loading f/w image
 * [in]     value[2].a:   Firmware image size
 */
#define PTA_BCM_ELOG_CMD_LOAD_NITRO_FW		3

#define BCM_ELOG_GLOBAL_METADATA_SIG		0x45524c47

#define MAX_NITRO_CRASH_DUMP_MEM_SIZE		0x2000000
#define MAX_NITRO_FW_LOAD_MEM_SIZE		0x200000

/* Load Nitro fw image to SEC DDR memory */
static TEE_Result pta_elog_load_nitro_fw(uint32_t param_types,
					 TEE_Param params[TEE_NUM_PARAMS])
{
	TEE_Result res = TEE_SUCCESS;
	paddr_t src_paddr = BCM_NITRO_FW_LOAD_ADDR + BNXT_IMG_SECMEM_OFFSET;
	vaddr_t src_vaddr = 0;
	uint32_t offset = 0;
	size_t end_offs = 0;
	size_t sz = 0;
	char *buf = NULL;
	uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
						   TEE_PARAM_TYPE_VALUE_INPUT,
						   TEE_PARAM_TYPE_VALUE_INPUT,
						   TEE_PARAM_TYPE_NONE);

	if (exp_param_types != param_types) {
		EMSG("Invalid Param types");
		return TEE_ERROR_BAD_PARAMETERS;
	}

	/* Check if firmware file size exceeds reserved memory size */
	if (params[2].value.a > MAX_NITRO_FW_LOAD_MEM_SIZE) {
		EMSG("Invalid access");
		return TEE_ERROR_EXCESS_DATA;
	}

	offset = params[1].value.a;
	buf = params[0].memref.buffer;
	sz = params[0].memref.size;

	/*
	 * Check that we under no circumstances will attempt to write
	 * beyond BCM_NITRO_FW_LOAD_ADDR + MAX_NITRO_FW_LOAD_MEM_SIZE.
	 */
	if (ADD_OVERFLOW(sz, offset, &end_offs) ||
	    end_offs > MAX_NITRO_FW_LOAD_MEM_SIZE - BNXT_IMG_SECMEM_OFFSET) {
		EMSG("Invalid access");
		return TEE_ERROR_ACCESS_DENIED;
	}

	src_vaddr = (vaddr_t)phys_to_virt((uintptr_t)src_paddr + offset,
					  MEM_AREA_RAM_SEC, sz);
	if (!src_vaddr) {
		EMSG("Not enough memory mapped");
		return TEE_ERROR_BAD_PARAMETERS;
	}

	memcpy((char *)src_vaddr, buf, sz);

	cache_op_inner(DCACHE_AREA_CLEAN, (void *)src_vaddr, sz);

	return res;
}

static uint32_t get_dump_data(vaddr_t src, TEE_Param params[TEE_NUM_PARAMS])
{
	char *buf = NULL;
	uint32_t sz = 0;

	buf = params[0].memref.buffer;
	sz = params[0].memref.size;

	/*
	 * If request size exceeds default buf size
	 * override request size to default DEFAULT_ELOG_BUFFER_SIZE
	 */
	if (sz > DEFAULT_ELOG_BUFFER_SIZE)
		sz = DEFAULT_ELOG_BUFFER_SIZE;

	DMSG("buf %p sz 0x%x", buf, sz);

	memcpy(buf, (char *)src, sz);

	params[0].memref.size = sz;

	return sz;
}

/* Copy nitro crash dump data */
static TEE_Result pta_elog_nitro_crash_dump(uint32_t param_types,
					    TEE_Param params[TEE_NUM_PARAMS])
{
	TEE_Result res = TEE_SUCCESS;
	paddr_t src_paddr = BCM_NITRO_CRASH_DUMP_BASE_ADDR;
	vaddr_t src_vaddr = 0;
	uint32_t offset = 0;
	uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT,
						   TEE_PARAM_TYPE_VALUE_INPUT,
						   TEE_PARAM_TYPE_NONE,
						   TEE_PARAM_TYPE_NONE);
	uint32_t sz = 0;

	if (exp_param_types != param_types) {
		EMSG("Invalid Param types");
		return TEE_ERROR_BAD_PARAMETERS;
	}

	offset = params[1].value.a;

	/*
	 * Check if offset is within memory range reserved for nitro crash dump
	 * minus default size of buffer
	 */
	if (offset > MAX_NITRO_CRASH_DUMP_MEM_SIZE - DEFAULT_ELOG_BUFFER_SIZE) {
		EMSG("Invalid access");
		return TEE_ERROR_ACCESS_DENIED;
	}

	sz = MIN(params[0].memref.size, DEFAULT_ELOG_BUFFER_SIZE);
	src_vaddr = (vaddr_t)phys_to_virt((uintptr_t)src_paddr + offset,
					  MEM_AREA_RAM_SEC, sz);
	if (!src_vaddr) {
		EMSG("Not enough memory mapped");
		return TEE_ERROR_BAD_PARAMETERS;
	}

	/* TODO : check if NITRO_CRASH_DUMP is available */

	cache_op_inner(DCACHE_AREA_INVALIDATE, (void *)src_vaddr,
		       DEFAULT_ELOG_BUFFER_SIZE);

	get_dump_data(src_vaddr, params);

	return res;
}

/* Copy soc error log data */
static TEE_Result pta_elog_dump(uint32_t param_types,
				TEE_Param params[TEE_NUM_PARAMS])
{
	TEE_Result res = TEE_SUCCESS;
	paddr_t src_paddr = CFG_BCM_ELOG_BASE;
	vaddr_t src_vaddr = 0;
	uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT,
						   TEE_PARAM_TYPE_VALUE_INPUT,
						   TEE_PARAM_TYPE_NONE,
						   TEE_PARAM_TYPE_NONE);
	uint32_t sz = 0;

	if (exp_param_types != param_types) {
		EMSG("Invalid Param types");
		return TEE_ERROR_BAD_PARAMETERS;
	}

	sz = MIN(params[0].memref.size, DEFAULT_ELOG_BUFFER_SIZE);
	src_vaddr = (vaddr_t)phys_to_virt(src_paddr, MEM_AREA_RAM_NSEC, sz);
	if (!src_vaddr) {
		EMSG("Not enough memory mapped");
		return TEE_ERROR_BAD_PARAMETERS;
	}

	/* Validate if Error logs are present */
	if ((*(uint32_t *)src_vaddr) != BCM_ELOG_GLOBAL_METADATA_SIG) {
		EMSG("Elog Not setup");
		return TEE_ERROR_NOT_SUPPORTED;
	}

	get_dump_data(src_vaddr, params);

	return res;
}

static TEE_Result invoke_command(void *session_context __unused,
				 uint32_t cmd_id,
				 uint32_t param_types,
				 TEE_Param params[TEE_NUM_PARAMS])
{
	TEE_Result res = TEE_SUCCESS;

	DMSG("command entry point[%d] for \"%s\"", cmd_id, ELOG_TA_NAME);

	switch (cmd_id) {
	case PTA_BCM_ELOG_CMD_GET_ELOG_MEM:
		res = pta_elog_dump(param_types, params);
		break;
	case PTA_BCM_ELOG_CMD_GET_NITRO_CRASH_DUMP:
		res = pta_elog_nitro_crash_dump(param_types, params);
		break;
	case PTA_BCM_ELOG_CMD_LOAD_NITRO_FW:
		res = pta_elog_load_nitro_fw(param_types, params);
		break;
	default:
		EMSG("cmd: %d Not supported %s", cmd_id, ELOG_TA_NAME);
		res = TEE_ERROR_NOT_SUPPORTED;
		break;
	}

	return res;
}

pseudo_ta_register(.uuid = ELOG_SERVICE_UUID,
		   .name = ELOG_TA_NAME,
		   .flags = PTA_DEFAULT_FLAGS,
		   .invoke_command_entry_point = invoke_command);