/*
 * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <platform_def.h>

#include <common/debug.h>
#include <drivers/cfi/v2m_flash.h>
#include <lib/psci/psci.h>
#include <lib/mmio.h>
#include <lib/utils.h>
#include <plat/arm/common/plat_arm.h>

/*
 * DRAM1 is used also to load the NS boot loader. For this reason we
 * cannot clear the full DRAM1, because in that case we would clear
 * the NS images (especially for RESET_TO_BL31 and RESET_TO_SPMIN cases).
 * For this reason we reserve 64 MB for the NS images and protect the RAM
 * until the end of DRAM1.
 * We limit the size of DRAM2 to 1 GB to avoid big delays while booting
 */
#define DRAM1_NS_IMAGE_LIMIT  (PLAT_ARM_NS_IMAGE_OFFSET + (32 << TWO_MB_SHIFT))
#define DRAM1_PROTECTED_SIZE  (ARM_NS_DRAM1_END+1u - DRAM1_NS_IMAGE_LIMIT)

static mem_region_t arm_ram_ranges[] = {
	{DRAM1_NS_IMAGE_LIMIT, DRAM1_PROTECTED_SIZE},
#ifdef AARCH64
	{ARM_DRAM2_BASE, 1u << ONE_GB_SHIFT},
#endif
};

/*******************************************************************************
 * Function that reads the content of the memory protect variable that
 * enables clearing of non secure memory when system boots. This variable
 * should be stored in a secure NVRAM.
 ******************************************************************************/
int arm_psci_read_mem_protect(int *enabled)
{
	int tmp;

	tmp = *(int *) PLAT_ARM_MEM_PROT_ADDR;
	*enabled = (tmp == 1) ? 1 : 0;
	return 0;
}

/*******************************************************************************
 * Function that writes the content of the memory protect variable that
 * enables overwritten of non secure memory when system boots.
 ******************************************************************************/
int arm_nor_psci_write_mem_protect(int val)
{
	unsigned long enable = (val != 0) ? 1UL : 0UL;

	if (nor_unlock(PLAT_ARM_MEM_PROT_ADDR) != 0) {
		ERROR("unlocking memory protect variable\n");
		return -1;
	}

	if (enable == 1UL) {
		/*
		 * If we want to write a value different than 0
		 * then we have to erase the full block because
		 * otherwise we cannot ensure that the value programmed
		 * into the flash is going to be the same than the value
		 * requested by the caller
		 */
		if (nor_erase(PLAT_ARM_MEM_PROT_ADDR) != 0) {
			ERROR("erasing block containing memory protect variable\n");
			return -1;
		}
	}

	if (nor_word_program(PLAT_ARM_MEM_PROT_ADDR, enable) != 0) {
		ERROR("programming memory protection variable\n");
		return -1;
	}
	return 0;
}

/*******************************************************************************
 * Function used for required psci operations performed when
 * system boots
 ******************************************************************************/
/*
 * PLAT_MEM_PROTECT_VA_FRAME is a address specifically
 * selected in a way that is not needed an additional
 * translation table for memprotect. It happens because
 * we use a chunk of size 2MB and it means that it can
 * be mapped in a level 2 table and the level 2 table
 * for 0xc0000000 is already used and the entry for
 * 0xc0000000 is not used.
 */
#if defined(PLAT_XLAT_TABLES_DYNAMIC)
void arm_nor_psci_do_dyn_mem_protect(void)
{
	int enable;

	arm_psci_read_mem_protect(&enable);
	if (enable == 0)
		return;

	INFO("PSCI: Overwriting non secure memory\n");
	clear_map_dyn_mem_regions(arm_ram_ranges,
				  ARRAY_SIZE(arm_ram_ranges),
				  PLAT_ARM_MEM_PROTEC_VA_FRAME,
				  1 << TWO_MB_SHIFT);
}
#endif

/*******************************************************************************
 * Function used for required psci operations performed when
 * system boots and dynamic memory is not used.
 ******************************************************************************/
void arm_nor_psci_do_static_mem_protect(void)
{
	int enable;

	(void) arm_psci_read_mem_protect(&enable);
	if (enable == 0)
		return;

	INFO("PSCI: Overwriting non secure memory\n");
	clear_mem_regions(arm_ram_ranges,
			  ARRAY_SIZE(arm_ram_ranges));
	(void) arm_nor_psci_write_mem_protect(0);
}

/*******************************************************************************
 * Function that checks if a region is protected by the memory protect
 * mechanism
 ******************************************************************************/
int arm_psci_mem_protect_chk(uintptr_t base, u_register_t length)
{
	return mem_region_in_array_chk(arm_ram_ranges,
				       ARRAY_SIZE(arm_ram_ranges),
				       base, length);
}
