/* Copyright 2013 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.
 */

/* Flash module for emulator */

#include <stdio.h>

#include "common.h"
#include "config_chip.h"
#include "flash.h"
#include "persistence.h"
#include "util.h"

/* This needs to be aligned to the erase bank size for NVCTR. */
__aligned(CONFIG_FLASH_ERASE_SIZE) char __host_flash[CONFIG_FLASH_SIZE];
uint8_t __host_flash_protect[PHYSICAL_BANKS];

/* Override this function to make flash erase/write operation fail */
test_mockable int flash_pre_op(void)
{
	return EC_SUCCESS;
}

static int flash_check_protect(int offset, int size)
{
	int first_bank = offset / CONFIG_FLASH_BANK_SIZE;
	int last_bank = DIV_ROUND_UP(offset + size,
				     CONFIG_FLASH_BANK_SIZE);
	int bank;

	for (bank = first_bank; bank < last_bank; ++bank)
		if (__host_flash_protect[bank])
			return 1;
	return 0;
}

static void flash_set_persistent(void)
{
	FILE *f = get_persistent_storage("flash", "wb");
	int sz;

	ASSERT(f != NULL);

	sz = fwrite(__host_flash, sizeof(__host_flash), 1, f);
	ASSERT(sz == 1);

	release_persistent_storage(f);
}

static void flash_get_persistent(void)
{
	FILE *f = get_persistent_storage("flash", "rb");

	if (f == NULL) {
		fprintf(stderr,
			"No flash storage found. Initializing to 0xff.\n");
		memset(__host_flash, 0xff, sizeof(__host_flash));
		return;
	}

	fread(__host_flash, sizeof(__host_flash), 1, f);

	release_persistent_storage(f);
}

int flash_physical_write(int offset, int size, const char *data)
{
	ASSERT((size & (CONFIG_FLASH_WRITE_SIZE - 1)) == 0);

	if (flash_pre_op() != EC_SUCCESS)
		return EC_ERROR_UNKNOWN;

	if (flash_check_protect(offset, size))
		return EC_ERROR_ACCESS_DENIED;

	memcpy(__host_flash + offset, data, size);
	flash_set_persistent();

	return EC_SUCCESS;
}

int flash_physical_erase(int offset, int size)
{
	ASSERT((size & (CONFIG_FLASH_ERASE_SIZE - 1)) == 0);

	if (flash_pre_op() != EC_SUCCESS)
		return EC_ERROR_UNKNOWN;

	if (flash_check_protect(offset, size))
		return EC_ERROR_ACCESS_DENIED;

	memset(__host_flash + offset, 0xff, size);
	flash_set_persistent();

	return EC_SUCCESS;
}

int flash_physical_get_protect(int bank)
{
	return __host_flash_protect[bank];
}

uint32_t flash_physical_get_protect_flags(void)
{
	int i;
	uint32_t flags = EC_FLASH_PROTECT_ALL_NOW;

	for (i = 0; i < PHYSICAL_BANKS; ++i)
		if (__host_flash_protect[i] == 0)
			flags = 0;

	return flags;
}

int flash_physical_protect_now(int all)
{
	memset(__host_flash_protect, 1, all ? PHYSICAL_BANKS : WP_BANK_COUNT);
	return EC_SUCCESS;
}

uint32_t flash_physical_get_valid_flags(void)
{
	return EC_FLASH_PROTECT_RO_AT_BOOT |
	       EC_FLASH_PROTECT_RO_NOW |
	       EC_FLASH_PROTECT_ALL_NOW;
}

uint32_t flash_physical_get_writable_flags(uint32_t cur_flags)
{
	uint32_t ret = 0;

	/* If RO protection isn't enabled, its at-boot state can be changed. */
	if (!(cur_flags & EC_FLASH_PROTECT_RO_NOW))
		ret |= EC_FLASH_PROTECT_RO_AT_BOOT;

	/*
	 * If entire flash isn't protected at this boot, it can be enabled if
	 * the WP GPIO is asserted.
	 */
	if (!(cur_flags & EC_FLASH_PROTECT_ALL_NOW) &&
	    (cur_flags & EC_FLASH_PROTECT_GPIO_ASSERTED))
		ret |= EC_FLASH_PROTECT_ALL_NOW;

	return ret;
}

int flash_pre_init(void)
{
	uint32_t prot_flags;

	flash_get_persistent();

	prot_flags = flash_get_protect();

	if (prot_flags & EC_FLASH_PROTECT_GPIO_ASSERTED) {
		/*
		 * Write protect is asserted.  If we want RO flash protected,
		 * protect it now.
		 */
		if ((prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT) &&
		    !(prot_flags & EC_FLASH_PROTECT_RO_NOW)) {
			int rv = flash_set_protect(EC_FLASH_PROTECT_RO_NOW,
						   EC_FLASH_PROTECT_RO_NOW);
			if (rv)
				return rv;

			/* Re-read flags */
			prot_flags = flash_get_protect();
		}
	}

	return EC_SUCCESS;
}
