/* Copyright 2015 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * The function prototypes were extracted from the TCG Published
 * Trusted Platform Module Library
 * Part 4: Supporting Routines
 * Family "2.0"
 * Level 00 Revision 01.16
 * October 30, 2014
 */

#include <string.h>

#include "PlatformData.h"
#include "TpmError.h"
#include "assert.h"
#include "nvmem.h"

/* Local state */
static struct {
#ifndef CONFIG_FLASH_NVMEM
	uint8_t s_NV[NV_MEMORY_SIZE];
#endif
	BOOL s_NvIsAvailable;
	BOOL s_NV_unrecoverable;
	BOOL s_NV_recoverable;
} local_state  __attribute__((section(".bss.Tpm2_common")));
/*
 * This function is used by the simulator to set the error flags in the NV
 * subsystem to simulate an error in the NV loading process.
 */
void _plat__NvErrors(BOOL recoverable, BOOL unrecoverable)
{
	local_state.s_NV_unrecoverable = unrecoverable;
	local_state.s_NV_recoverable = recoverable;
}

/*
 * This function should retrieve and verify the integrity of the saved context.
 * On success, the NV content has been copied into local RAM for fast access.
 *
 * The recovery from an integrity failure depends on where the error occurred.
 * It it was in the state that is discarded by TPM Reset, then the error is
 * recoverable if the TPM is reset. Otherwise, the TPM must go into failure
 * mode.
 *
 *     Return Value                      Meaning
 *
 *     0                                 if success
 *     >0                                if receive recoverable error
 *     <0                                if unrecoverable error
 */
int _plat__NVEnable(void *platParameter)
{
	local_state.s_NV_unrecoverable = FALSE;
	local_state.s_NV_recoverable = FALSE;

#ifdef CONFIG_FLASH_NVMEM
	/* TODO: Need to define what is recoverable and unrecoverable
	 * conditions with regards to NvMem module. For now, the only
	 * requirement is that at Cr50 board initialization time, the
	 * nvmem_init() function either detects a valid partition, or
	 * determines that NvMem is fully erased and configures  a valid
	 * partition. Setting both variables TRUE if NvMem is not available
	 */
	local_state.s_NV_recoverable = nvmem_get_error_state() != 0;
	local_state.s_NV_unrecoverable = local_state.s_NV_recoverable;
#endif
	if (local_state.s_NV_unrecoverable)
		return -1;
	return local_state.s_NV_recoverable;
}

void _plat__NVDisable(void)
{
	/* nothing yet */
}

/*
 * Check if NV is available
 *
 *      Return Value                      Meaning
 *
 *      0                                 NV is available
 *      1                                 NV not available due to write failure
 *      2                                 NV not available due to rate limit
 */
int _plat__IsNvAvailable(void)
{

#ifdef CONFIG_FLASH_NVMEM
	int rv;
	/*
	 * sNv_IsAvailable is a state variable that can be accesed by the
	 * simmulator to control access to NvMemory. This variable and
	 * the on chip NvMem area must be in the correct state for NvMem
	 * to be in 'NV is available' state.
	 */
	rv = !local_state.s_NvIsAvailable || nvmem_get_error_state();
	return rv;
#else
	if (!local_state.s_NvIsAvailable)
		return 1;

	return 0;
#endif
}

/*
 * Read a chunk of NV memory
 */
void _plat__NvMemoryRead(unsigned int startOffset,
				    unsigned int size,
				    void *data)
{
	assert(startOffset + size <= NV_MEMORY_SIZE);
	/* Copy the data from the NV image */
#ifdef CONFIG_FLASH_NVMEM
	nvmem_read(startOffset, size, data, NVMEM_TPM);
#else
	memcpy(data, &local_state.s_NV[startOffset], size);
#endif
	return;
}

/*
 * This function tests to see if the NV is different from the test value.
 * Returns true if different, false if not.
 */
BOOL
_plat__NvIsDifferent(unsigned int startOffset,
		     unsigned int size,
		     void *data)
{
#ifdef CONFIG_FLASH_NVMEM
	return (nvmem_is_different(startOffset, size, data, NVMEM_TPM) != 0);
#else
	return !DCRYPTO_equals(&local_state.s_NV[startOffset], data, size);
#endif
}

/*
 * This function is used to update NV memory. The write is to a memory copy of
 * NV. At the end of the current command, any changes are written to the actual
 * NV memory.
 */
void _plat__NvMemoryWrite(unsigned int startOffset,
			  unsigned int size,
			  void *data)
{
	assert(startOffset + size <= NV_MEMORY_SIZE);
	/* Copy the data to the NV image */
#ifdef CONFIG_FLASH_NVMEM
	nvmem_write(startOffset, size, data, NVMEM_TPM);
#else
	memcpy(&local_state.s_NV[startOffset], data, size);
#endif
}

/*
 *      Function: Move a chunk of NV memory from source to destination,
 *      handling overlap correctly.
 */
void _plat__NvMemoryMove(unsigned int sourceOffset,
				    unsigned int destOffset,
				    unsigned int size)
{
	assert(sourceOffset + size <= NV_MEMORY_SIZE);
	assert(destOffset + size <= NV_MEMORY_SIZE);
#ifdef CONFIG_FLASH_NVMEM
	nvmem_move(sourceOffset, destOffset, size, NVMEM_TPM);
#else
	/* Move data in RAM */
	memmove(&local_state.s_NV[destOffset],
		&local_state.s_NV[sourceOffset], size);
#endif
	return;
}

/*
 * Commit the local RAM copy to NV storage.
 *
 *      Return Value                      Meaning
 *
 *      0                                 NV write success
 *      non-0                             NV write fail
 */
int _plat__NvCommit(void)
{
#ifdef CONFIG_FLASH_NVMEM
	return nvmem_commit();
#else
	return 0;
#endif
}

/*
 * Set the current NV state to available. This function is for testing purpose
 * only. It is not part of the platform NV logic
 */
void _plat__SetNvAvail(void)
{
	local_state.s_NvIsAvailable = TRUE;
	return;
}

/*
 * Set the current NV state to unavailable. This function is for testing
 * purpose only. It is not part of the platform NV logic
 */
void _plat__ClearNvAvail(void)
{
	local_state.s_NvIsAvailable = FALSE;
	return;
}
