/* Copyright 2012 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 memory module for Chrome EC - common functions */

#include "common.h"
#include "console.h"
#include "flash.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "otp.h"
#include "rwsig.h"
#include "shared_mem.h"
#include "system.h"
#include "util.h"
#include "vboot_hash.h"

/*
 * Contents of erased flash, as a 32-bit value.  Most platforms erase flash
 * bits to 1.
 */
#ifndef CONFIG_FLASH_ERASED_VALUE32
#define CONFIG_FLASH_ERASED_VALUE32 (-1U)
#endif

#ifdef CONFIG_FLASH_PSTATE

/*
 * If flash isn't mapped to the EC's address space, it's probably SPI, and
 * should be using SPI write protect, not PSTATE.
 */
#if !defined(CONFIG_INTERNAL_STORAGE) || !defined(CONFIG_MAPPED_STORAGE)
#error "PSTATE should only be used with internal mem-mapped flash."
#endif

#ifdef CONFIG_FLASH_PSTATE_BANK
/* Persistent protection state - emulates a SPI status register for flashrom */
/* NOTE: It's not expected that RO and RW will support
 * differing PSTATE versions. */
#define PERSIST_STATE_VERSION 3  /* Expected persist_state.version */

/* Flags for persist_state.flags */
/* Protect persist state and RO firmware at boot */
#define PERSIST_FLAG_PROTECT_RO 0x02
#define PSTATE_VALID_FLAGS      BIT(0)
#define PSTATE_VALID_SERIALNO   BIT(1)
#define PSTATE_VALID_MAC_ADDR   BIT(2)

struct persist_state {
	uint8_t version;            /* Version of this struct */
	uint8_t flags;              /* Lock flags (PERSIST_FLAG_*) */
	uint8_t valid_fields;       /* Flags for valid data. */
	uint8_t reserved;           /* Reserved; set 0 */
#ifdef CONFIG_SERIALNO_LEN
	uint8_t serialno[CONFIG_SERIALNO_LEN]; /* Serial number. */
#endif /* CONFIG_SERIALNO_LEN */
#ifdef CONFIG_MAC_ADDR_LEN
	uint8_t mac_addr[CONFIG_MAC_ADDR_LEN];
#endif /* CONFIG_MAC_ADDR_LEN */
#if !defined(CONFIG_SERIALNO_LEN) && !defined(CONFIG_MAC_ADDR_LEN)
	uint8_t padding[4 % CONFIG_FLASH_WRITE_SIZE];
#endif
};

/* written with flash_physical_write, need to respect alignment constraints */
#ifndef CHIP_FAMILY_STM32L /* STM32L1xx is somewhat lying to us */
BUILD_ASSERT(sizeof(struct persist_state) % CONFIG_FLASH_WRITE_SIZE == 0);
#endif

BUILD_ASSERT(sizeof(struct persist_state) <= CONFIG_FW_PSTATE_SIZE);

#else /* !CONFIG_FLASH_PSTATE_BANK */

/*
 * Flags for write protect state depend on the erased value of flash.  The
 * locked value must be the same as the unlocked value with one or more bits
 * transitioned away from the erased state.  That way, it is possible to
 * rewrite the data in-place to set the lock.
 *
 * STM32F0x can only write 0x0000 to a non-erased half-word, which means
 * PSTATE_MAGIC_LOCKED isn't quite as pretty.  That's ok; the only thing
 * we actually need to detect is PSTATE_MAGIC_UNLOCKED, since that's the
 * only value we'll ever alter, and the only value which causes us not to
 * lock the flash at boot.
 */
#if (CONFIG_FLASH_ERASED_VALUE32 == -1U)
#define PSTATE_MAGIC_UNLOCKED 0x4f4e5057  /* "WPNO" */
#define PSTATE_MAGIC_LOCKED   0x00000000  /* ""     */
#elif (CONFIG_FLASH_ERASED_VALUE32 == 0)
#define PSTATE_MAGIC_UNLOCKED 0x4f4e5057  /* "WPNO" */
#define PSTATE_MAGIC_LOCKED   0x5f5f5057  /* "WP__" */
#else
/* What kind of wacky flash doesn't erase all bits to 1 or 0? */
#error "PSTATE needs magic values for this flash architecture."
#endif

/*
 * Rewriting the write protect flag in place currently requires a minimum write
 * size <= the size of the flag value.
 *
 * We could work around this on chips with larger minimum write size by reading
 * the write block containing the flag into RAM, changing it to the locked
 * value, and then rewriting that block.  But we should only pay for that
 * complexity when we run across another chip which needs it.
 */
#if (CONFIG_FLASH_WRITE_SIZE > 4)
#error "Non-bank-based PSTATE requires flash write size <= 32 bits."
#endif

const uint32_t pstate_data __attribute__((section(".rodata.pstate"))) =
#ifdef CONFIG_FLASH_PSTATE_LOCKED
	PSTATE_MAGIC_LOCKED;
#else
	PSTATE_MAGIC_UNLOCKED;
#endif

#endif /* !CONFIG_FLASH_PSTATE_BANK */
#endif /* CONFIG_FLASH_PSTATE */

#ifdef CONFIG_FLASH_MULTIPLE_REGION
const struct ec_flash_bank *flash_bank_info(int bank)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(flash_bank_array); i++) {
		if (bank < flash_bank_array[i].count)
			return &flash_bank_array[i];
		bank -= flash_bank_array[i].count;
	}

	return NULL;
}

int flash_bank_size(int bank)
{
	int rv;
	const struct ec_flash_bank *info = flash_bank_info(bank);

	if (!info)
		return -1;

	rv = BIT(info->size_exp);
	ASSERT(rv > 0);
	return rv;
}

int flash_bank_erase_size(int bank)
{
	int rv;
	const struct ec_flash_bank *info = flash_bank_info(bank);

	if (!info)
		return -1;

	rv = BIT(info->erase_size_exp);
	ASSERT(rv > 0);
	return rv;
}

int flash_bank_index(int offset)
{
	int bank_offset = 0, i;

	if (offset == 0)
		return bank_offset;

	for (i = 0; i < ARRAY_SIZE(flash_bank_array); i++) {
		int all_sector_size = flash_bank_array[i].count <<
			flash_bank_array[i].size_exp;
		if (offset >= all_sector_size) {
			offset -= all_sector_size;
			bank_offset += flash_bank_array[i].count;
			continue;
		}
		if (offset & ((1 << flash_bank_array[i].size_exp) - 1))
			return -1;
		return bank_offset + (offset >> flash_bank_array[i].size_exp);
	}
	if (offset != 0)
		return -1;
	return bank_offset;
}

int flash_bank_count(int offset, int size)
{
	int begin = flash_bank_index(offset);
	int end = flash_bank_index(offset + size);

	if (begin == -1 || end == -1)
		return -1;
	return end - begin;
}

int flash_bank_start_offset(int bank)
{
	int i;
	int offset;
	int bank_size;

	if (bank < 0)
		return -1;

	offset = 0;
	for (i = 0; i < bank; i++) {
		bank_size = flash_bank_size(i);
		if (bank_size < 0)
			return -1;
		offset += bank_size;
	}

	return offset;
}

#endif  /* CONFIG_FLASH_MULTIPLE_REGION */

static int flash_range_ok(int offset, int size_req, int align)
{
	if (offset < 0 || size_req < 0 ||
	    offset > CONFIG_FLASH_SIZE ||
	    size_req > CONFIG_FLASH_SIZE ||
	    offset + size_req > CONFIG_FLASH_SIZE ||
	    (offset | size_req) & (align - 1))
		return 0;  /* Invalid range */

	return 1;
}

#ifdef CONFIG_MAPPED_STORAGE
/**
 * Get the physical memory address of a flash offset
 *
 * This is used for direct flash access. We assume that the flash is
 * contiguous from this start address through to the end of the usable
 * flash.
 *
 * @param offset	Flash offset to get address of
 * @param dataptrp	Returns pointer to memory address of flash offset
 * @return pointer to flash memory offset, if ok, else NULL
 */
static const char *flash_physical_dataptr(int offset)
{
	return (char *)((uintptr_t)CONFIG_MAPPED_STORAGE_BASE + offset);
}

int flash_dataptr(int offset, int size_req, int align, const char **ptrp)
{
	if (!flash_range_ok(offset, size_req, align))
		return -1;  /* Invalid range */
	if (ptrp)
		*ptrp = flash_physical_dataptr(offset);

	return CONFIG_FLASH_SIZE - offset;
}
#endif

#ifdef CONFIG_FLASH_PSTATE
#ifdef CONFIG_FLASH_PSTATE_BANK

/**
 * Read and return persistent state flags (EC_FLASH_PROTECT_*)
 */
static uint32_t flash_read_pstate(void)
{
	const struct persist_state *pstate =
		(const struct persist_state *)
		flash_physical_dataptr(CONFIG_FW_PSTATE_OFF);

	if ((pstate->version == PERSIST_STATE_VERSION) &&
	    (pstate->valid_fields & PSTATE_VALID_FLAGS) &&
	    (pstate->flags & PERSIST_FLAG_PROTECT_RO)) {
		/* Lock flag is known to be set */
		return EC_FLASH_PROTECT_RO_AT_BOOT;
	} else {
#ifdef CONFIG_WP_ALWAYS
		return PERSIST_FLAG_PROTECT_RO;
#else
		return 0;
#endif
	}
}

/**
 * Write persistent state after erasing.
 *
 * @param pstate	New data to set in pstate. NOT memory mapped
 *                      old pstate as it will be erased.
 * @return EC_SUCCESS, or nonzero if error.
 */
static int flash_write_pstate_data(struct persist_state *newpstate)
{
	int rv;

	/* Erase pstate */
	rv = flash_physical_erase(CONFIG_FW_PSTATE_OFF,
				  CONFIG_FW_PSTATE_SIZE);
	if (rv)
		return rv;

	/*
	 * Note that if we lose power in here, we'll lose the pstate contents.
	 * That's ok, because it's only possible to write the pstate before
	 * it's protected.
	 */

	/* Write the updated pstate */
	return flash_physical_write(CONFIG_FW_PSTATE_OFF, sizeof(*newpstate),
				    (const char *)newpstate);
}



/**
 * Validate and Init persistent state datastructure.
 *
 * @param pstate	A pstate data structure. Will be valid at complete.
 * @return EC_SUCCESS, or nonzero if error.
 */
static int validate_pstate_struct(struct persist_state *pstate)
{
	if (pstate->version != PERSIST_STATE_VERSION) {
		memset(pstate, 0, sizeof(*pstate));
		pstate->version = PERSIST_STATE_VERSION;
	}

	return EC_SUCCESS;
}

/**
 * Write persistent state from pstate, erasing if necessary.
 *
 * @param flags		New flash write protect flags to set in pstate.
 * @return EC_SUCCESS, or nonzero if error.
 */
static int flash_write_pstate(uint32_t flags)
{
	struct persist_state newpstate;
	const struct persist_state *pstate =
		(const struct persist_state *)
		flash_physical_dataptr(CONFIG_FW_PSTATE_OFF);

	/* Only check the flags we write to pstate */
	flags &= EC_FLASH_PROTECT_RO_AT_BOOT;

	/* Check if pstate has actually changed */
	if (flags == flash_read_pstate())
		return EC_SUCCESS;

	/* Cache the old copy for read/modify/write. */
	memcpy(&newpstate, pstate, sizeof(newpstate));
	validate_pstate_struct(&newpstate);

	if (flags & EC_FLASH_PROTECT_RO_AT_BOOT)
		newpstate.flags |= PERSIST_FLAG_PROTECT_RO;
	else
		newpstate.flags &= ~PERSIST_FLAG_PROTECT_RO;
	newpstate.valid_fields |= PSTATE_VALID_FLAGS;

	return flash_write_pstate_data(&newpstate);
}

#ifdef CONFIG_SERIALNO_LEN
/**
 * Read and return persistent serial number.
 */
const char *flash_read_pstate_serial(void)
{
	const struct persist_state *pstate =
		(const struct persist_state *)
		flash_physical_dataptr(CONFIG_FW_PSTATE_OFF);

	if ((pstate->version == PERSIST_STATE_VERSION) &&
	    (pstate->valid_fields & PSTATE_VALID_SERIALNO)) {
		return (const char *)(pstate->serialno);
	}

	return NULL;
}

/**
 * Write persistent serial number to pstate, erasing if necessary.
 *
 * @param serialno		New ascii serial number to set in pstate.
 * @return EC_SUCCESS, or nonzero if error.
 */
int flash_write_pstate_serial(const char *serialno)
{
	int length;
	struct persist_state newpstate;
	const struct persist_state *pstate =
		(const struct persist_state *)
		flash_physical_dataptr(CONFIG_FW_PSTATE_OFF);

	/* Check that this is OK */
	if (!serialno)
		return EC_ERROR_INVAL;

	length = strnlen(serialno, sizeof(newpstate.serialno));
	if (length >= sizeof(newpstate.serialno)) {
		return EC_ERROR_INVAL;
	}

	/* Cache the old copy for read/modify/write. */
	memcpy(&newpstate, pstate, sizeof(newpstate));
	validate_pstate_struct(&newpstate);

	/*
	 * Erase any prior data and copy the string. The length was verified to
	 * be shorter than the buffer so a null terminator always remains.
	 */
	memset(newpstate.serialno, '\0', sizeof(newpstate.serialno));
	memcpy(newpstate.serialno, serialno, length);

	newpstate.valid_fields |= PSTATE_VALID_SERIALNO;

	return flash_write_pstate_data(&newpstate);
}

#endif /* CONFIG_SERIALNO_LEN */

#ifdef CONFIG_MAC_ADDR_LEN

/**
 * Read and return persistent MAC address.
 */
const char *flash_read_pstate_mac_addr(void)
{
	const struct persist_state *pstate =
		(const struct persist_state *)
		flash_physical_dataptr(CONFIG_FW_PSTATE_OFF);

	if ((pstate->version == PERSIST_STATE_VERSION) &&
	    (pstate->valid_fields & PSTATE_VALID_MAC_ADDR)) {
		return (const char *)(pstate->mac_addr);
	}

	return NULL;
}

/**
 * Write persistent MAC Addr to pstate, erasing if necessary.
 *
 * @param mac_addr		New ascii MAC address to set in pstate.
 * @return EC_SUCCESS, or nonzero if error.
 */
int flash_write_pstate_mac_addr(const char *mac_addr)
{
	int length;
	struct persist_state newpstate;
	const struct persist_state *pstate =
		(const struct persist_state *)
		flash_physical_dataptr(CONFIG_FW_PSTATE_OFF);

	/* Check that this is OK, data is valid and fits in the region. */
	if (!mac_addr) {
		return EC_ERROR_INVAL;
	}

	/*
	 * This will perform validation of the mac address before storing it.
	 * The MAC address format is '12:34:56:78:90:AB', a 17 character long
	 * string containing pairs of hex digits, each pair delimited by a ':'.
	 */
	length = strnlen(mac_addr, sizeof(newpstate.mac_addr));
	if (length != 17) {
		return EC_ERROR_INVAL;
	}
	for (int i = 0; i < 17; i++) {
		if (i % 3 != 2) {
			/* Verify the remaining characters are hex digits. */
			if ((mac_addr[i] < '0' || '9' < mac_addr[i]) &&
			    (mac_addr[i] < 'A' || 'F' < mac_addr[i]) &&
			    (mac_addr[i] < 'a' || 'f' < mac_addr[i])) {
				return EC_ERROR_INVAL;
			}
		} else {
			/* Every 3rd character is a ':' */
			if (mac_addr[i] != ':') {
				return EC_ERROR_INVAL;
			}
		}
	}

	/* Cache the old copy for read/modify/write. */
	memcpy(&newpstate, pstate, sizeof(newpstate));
	validate_pstate_struct(&newpstate);

	/*
	 * Erase any prior data and copy the string. The length was verified to
	 * be shorter than the buffer so a null terminator always remains.
	 */
	memset(newpstate.mac_addr, '\0', sizeof(newpstate.mac_addr));
	memcpy(newpstate.mac_addr, mac_addr, length);

	newpstate.valid_fields |= PSTATE_VALID_MAC_ADDR;

	return flash_write_pstate_data(&newpstate);
}

#endif /* CONFIG_MAC_ADDR_LEN */

#else /* !CONFIG_FLASH_PSTATE_BANK */

/**
 * Return the address of the pstate data in EC-RO.
 */
static const uintptr_t get_pstate_addr(void)
{
	uintptr_t addr = (uintptr_t)&pstate_data;

	/* Always use the pstate data in RO, even if we're RW */
	if (system_is_in_rw())
		addr += CONFIG_RO_MEM_OFF - CONFIG_RW_MEM_OFF;

	return addr;
}

/**
 * Read and return persistent state flags (EC_FLASH_PROTECT_*)
 */
static uint32_t flash_read_pstate(void)
{
	/* Check for the unlocked magic value */
	if (*(const uint32_t *)get_pstate_addr() == PSTATE_MAGIC_UNLOCKED)
		return 0;

	/* Anything else is locked */
	return EC_FLASH_PROTECT_RO_AT_BOOT;
}

/**
 * Write persistent state from pstate, erasing if necessary.
 *
 * @param flags		New flash write protect flags to set in pstate.
 * @return EC_SUCCESS, or nonzero if error.
 */
static int flash_write_pstate(uint32_t flags)
{
	const uint32_t new_pstate = PSTATE_MAGIC_LOCKED;

	/* Only check the flags we write to pstate */
	flags &= EC_FLASH_PROTECT_RO_AT_BOOT;

	/* Check if pstate has actually changed */
	if (flags == flash_read_pstate())
		return EC_SUCCESS;

	/* We can only set the protect flag, not clear it */
	if (!(flags & EC_FLASH_PROTECT_RO_AT_BOOT))
		return EC_ERROR_ACCESS_DENIED;

	/*
	 * Write a new pstate.  We can overwrite the existing value, because
	 * we're only moving bits from the erased state to the unerased state.
	 */
	return flash_physical_write(get_pstate_addr() -
				    CONFIG_PROGRAM_MEMORY_BASE,
				    sizeof(new_pstate),
				    (const char *)&new_pstate);
}

#endif /* !CONFIG_FLASH_PSTATE_BANK */
#endif /* CONFIG_FLASH_PSTATE */

int flash_is_erased(uint32_t offset, int size)
{
	const uint32_t *ptr;

#ifdef CONFIG_MAPPED_STORAGE
	/* Use pointer directly to flash */
	if (flash_dataptr(offset, size, sizeof(uint32_t),
			  (const char **)&ptr) < 0)
		return 0;

	flash_lock_mapped_storage(1);
	for (size /= sizeof(uint32_t); size > 0; size--, ptr++)
		if (*ptr != CONFIG_FLASH_ERASED_VALUE32) {
			flash_lock_mapped_storage(0);
			return 0;
	}

	flash_lock_mapped_storage(0);
#else
	/* Read flash a chunk at a time */
	uint32_t buf[8];
	int bsize;

	while (size) {
		bsize = MIN(size, sizeof(buf));

		if (flash_read(offset, bsize, (char *)buf))
			return 0;

		size -= bsize;
		offset += bsize;

		ptr = buf;
		for (bsize /= sizeof(uint32_t); bsize > 0; bsize--, ptr++)
			if (*ptr != CONFIG_FLASH_ERASED_VALUE32)
				return 0;

	}
#endif

	return 1;
}

int flash_read(int offset, int size, char *data)
{
#ifdef CONFIG_MAPPED_STORAGE
	const char *src;

	if (flash_dataptr(offset, size, 1, &src) < 0)
		return EC_ERROR_INVAL;

	flash_lock_mapped_storage(1);
	memcpy(data, src, size);
	flash_lock_mapped_storage(0);
	return EC_SUCCESS;
#else
	return flash_physical_read(offset, size, data);
#endif
}

static void flash_abort_or_invalidate_hash(int offset, int size)
{
#ifdef CONFIG_VBOOT_HASH
	if (vboot_hash_in_progress()) {
		/* Abort hash calculation when flash update is in progress. */
		vboot_hash_abort();
		return;
	}

#ifdef CONFIG_EXTERNAL_STORAGE
	/*
	 * If EC executes in RAM and is currently in RW, we keep the current
	 * hash. On the next hash check, AP will catch hash mismatch between the
	 * flash copy and the RAM copy, then take necessary actions.
	 */
	if (system_is_in_rw())
		return;
#endif

	/* If EC executes in place, we need to invalidate the cached hash. */
	vboot_hash_invalidate(offset, size);
#endif

#ifdef HAS_TASK_RWSIG
	/*
	 * If RW flash has been written to, make sure we do not automatically
	 * jump to RW after the timeout.
	 */
	if ((offset >= CONFIG_RW_MEM_OFF &&
		    offset < (CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE)) ||
	    ((offset + size) > CONFIG_RW_MEM_OFF &&
		    (offset + size) <= (CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE)) ||
	    (offset < CONFIG_RW_MEM_OFF &&
		    (offset + size) > (CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE)))
		rwsig_abort();
#endif
}

int flash_write(int offset, int size, const char *data)
{
	if (!flash_range_ok(offset, size, CONFIG_FLASH_WRITE_SIZE))
		return EC_ERROR_INVAL;  /* Invalid range */

	flash_abort_or_invalidate_hash(offset, size);

	return flash_physical_write(offset, size, data);
}

int flash_erase(int offset, int size)
{
#ifndef CONFIG_FLASH_MULTIPLE_REGION
	if (!flash_range_ok(offset, size, CONFIG_FLASH_ERASE_SIZE))
		return EC_ERROR_INVAL;  /* Invalid range */
#endif

	flash_abort_or_invalidate_hash(offset, size);

	return flash_physical_erase(offset, size);
}

int flash_protect_at_boot(uint32_t new_flags)
{
#ifdef CONFIG_FLASH_PSTATE
	uint32_t new_pstate_flags = new_flags & EC_FLASH_PROTECT_RO_AT_BOOT;

	/* Read the current persist state from flash */
	if (flash_read_pstate() != new_pstate_flags) {
		/* Need to update pstate */
		int rv;

#ifdef CONFIG_FLASH_PSTATE_BANK
		/* Fail if write protect block is already locked */
		if (flash_physical_get_protect(PSTATE_BANK))
			return EC_ERROR_ACCESS_DENIED;
#endif

		/* Write the desired flags */
		rv = flash_write_pstate(new_pstate_flags);
		if (rv)
			return rv;
	}

#ifdef CONFIG_FLASH_PROTECT_NEXT_BOOT
	/*
	 * Try updating at-boot protection state, if on a platform where write
	 * protection only changes after a reboot.  Otherwise we wouldn't
	 * update it until after the next reboot, and we'd need to reboot
	 * again.  Ignore errors, because the protection registers might
	 * already be locked this boot, and we'll still apply the correct state
	 * again on the next boot.
	 *
	 * This assumes PSTATE immediately follows RO, which it does on
	 * all STM32 platforms (which are the only ones with this config).
	 */
	flash_physical_protect_at_boot(new_flags);
#endif

	return EC_SUCCESS;
#else
	return flash_physical_protect_at_boot(new_flags);
#endif
}

uint32_t flash_get_protect(void)
{
	uint32_t flags = 0;
	int i;
	/* Region protection status */
	int not_protected[FLASH_REGION_COUNT] = {0};
#ifdef CONFIG_ROLLBACK
	/* Flags that must be set to set ALL_NOW flag. */
	const uint32_t all_flags = EC_FLASH_PROTECT_RO_NOW |
				   EC_FLASH_PROTECT_RW_NOW |
				   EC_FLASH_PROTECT_ROLLBACK_NOW;
#else
	const uint32_t all_flags = EC_FLASH_PROTECT_RO_NOW |
				   EC_FLASH_PROTECT_RW_NOW;
#endif

	/* Read write protect GPIO */
#ifdef CONFIG_WP_ALWAYS
	flags |= EC_FLASH_PROTECT_GPIO_ASSERTED;
#elif defined(CONFIG_WP_ACTIVE_HIGH)
	if (gpio_get_level(GPIO_WP))
		flags |= EC_FLASH_PROTECT_GPIO_ASSERTED;
#else
	if (!gpio_get_level(GPIO_WP_L))
		flags |= EC_FLASH_PROTECT_GPIO_ASSERTED;
#endif

#ifdef CONFIG_FLASH_PSTATE
	/* Read persistent state of RO-at-boot flag */
	flags |= flash_read_pstate();
#endif

	/* Scan flash protection */
	for (i = 0; i < PHYSICAL_BANKS; i++) {
		int is_ro = (i >= WP_BANK_OFFSET &&
			i < WP_BANK_OFFSET + WP_BANK_COUNT);
		enum flash_region region = is_ro ? FLASH_REGION_RO :
			FLASH_REGION_RW;
		int bank_flag = is_ro ? EC_FLASH_PROTECT_RO_NOW :
			EC_FLASH_PROTECT_RW_NOW;

#ifdef CONFIG_ROLLBACK
		if (i >= ROLLBACK_BANK_OFFSET &&
		    i < ROLLBACK_BANK_OFFSET + ROLLBACK_BANK_COUNT) {
			region = FLASH_REGION_ROLLBACK;
			bank_flag = EC_FLASH_PROTECT_ROLLBACK_NOW;
		}
#endif

		if (flash_physical_get_protect(i)) {
			/* At least one bank in the region is protected */
			flags |= bank_flag;
			if (not_protected[region])
				flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT;
		} else {
			/* At least one bank in the region is NOT protected */
			not_protected[region] = 1;
			if (flags & bank_flag)
				flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT;
		}
	}

	if ((flags & all_flags) == all_flags)
		flags |= EC_FLASH_PROTECT_ALL_NOW;

	/*
	 * If the RW or ROLLBACK banks are protected but the RO banks aren't,
	 * that's inconsistent.
	 *
	 * Note that we check this before adding in the physical flags below,
	 * since some chips can also protect ALL_NOW for the current boot by
	 * locking up the flash program-erase registers.
	 */
	if ((flags & all_flags) && !(flags & EC_FLASH_PROTECT_RO_NOW))
		flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT;

#ifndef CONFIG_FLASH_PROTECT_RW
	/* RW flag was used for intermediate computations, clear it now. */
	flags &= ~EC_FLASH_PROTECT_RW_NOW;
#endif

	/* Add in flags from physical layer */
	return flags | flash_physical_get_protect_flags();
}

/*
 * Request a flash protection flags change for |mask| flash protect flags
 * to |flags| state.
 *
 * Order of flag processing:
 * 1. Clear/Set RO_AT_BOOT + Clear *_AT_BOOT flags + Commit *_AT_BOOT flags.
 * 2. Return if RO_AT_BOOT and HW-WP are not asserted.
 * 3. Set remaining *_AT_BOOT flags + Commit *_AT_BOOT flags.
 * 4. Commit RO_NOW.
 * 5. Commit ALL_NOW.
 */
int flash_set_protect(uint32_t mask, uint32_t flags)
{
	int retval = EC_SUCCESS;
	int rv;
	int old_flags_at_boot = flash_get_protect() &
		(EC_FLASH_PROTECT_RO_AT_BOOT | EC_FLASH_PROTECT_RW_AT_BOOT |
			EC_FLASH_PROTECT_ROLLBACK_AT_BOOT |
			EC_FLASH_PROTECT_ALL_AT_BOOT);
	int new_flags_at_boot = old_flags_at_boot;

	/* Sanitize input flags */
	flags = flags & mask;

	/*
	 * Process flags we can set.  Track the most recent error, but process
	 * all flags before returning.
	 */

	/*
	 * AT_BOOT flags are trickier than NOW flags, as they can be set
	 * when HW write protection is disabled and can be unset without
	 * a reboot.
	 *
	 * If we are only setting/clearing RO_AT_BOOT, things are simple.
	 * Setting ALL_AT_BOOT is processed only if HW write protection is
	 * enabled and RO_AT_BOOT is set, so it's also simple.
	 *
	 * The most tricky one is when we want to clear ALL_AT_BOOT. We need
	 * to determine whether to clear protection for the entire flash or
	 * leave RO protected. There are two cases that we want to keep RO
	 * protected:
	 *   A. RO_AT_BOOT was already set before flash_set_protect() is
	 *      called.
	 *   B. RO_AT_BOOT was not set, but it's requested to be set by
	 *      the caller of flash_set_protect().
	 */

	/* 1.a - Clear RO_AT_BOOT. */
	new_flags_at_boot &= ~(mask & EC_FLASH_PROTECT_RO_AT_BOOT);
	/* 1.b - Set RO_AT_BOOT. */
	new_flags_at_boot |= flags & EC_FLASH_PROTECT_RO_AT_BOOT;

	/* 1.c - Clear ALL_AT_BOOT. */
	if ((mask & EC_FLASH_PROTECT_ALL_AT_BOOT) &&
	    !(flags & EC_FLASH_PROTECT_ALL_AT_BOOT)) {
		new_flags_at_boot &= ~EC_FLASH_PROTECT_ALL_AT_BOOT;
		/* Must also clear RW/ROLLBACK. */
#ifdef CONFIG_FLASH_PROTECT_RW
		new_flags_at_boot &= ~EC_FLASH_PROTECT_RW_AT_BOOT;
#endif
#ifdef CONFIG_ROLLBACK
		new_flags_at_boot &= ~EC_FLASH_PROTECT_ROLLBACK_AT_BOOT;
#endif
	}

	/* 1.d - Clear RW_AT_BOOT. */
#ifdef CONFIG_FLASH_PROTECT_RW
	if ((mask & EC_FLASH_PROTECT_RW_AT_BOOT) &&
	    !(flags & EC_FLASH_PROTECT_RW_AT_BOOT)) {
		new_flags_at_boot &= ~EC_FLASH_PROTECT_RW_AT_BOOT;
		/* Must also clear ALL (otherwise nothing will happen). */
		new_flags_at_boot &= ~EC_FLASH_PROTECT_ALL_AT_BOOT;
	}
#endif

	/* 1.e - Clear ROLLBACK_AT_BOOT. */
#ifdef CONFIG_ROLLBACK
	if ((mask & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT) &&
	    !(flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT)) {
		new_flags_at_boot &= ~EC_FLASH_PROTECT_ROLLBACK_AT_BOOT;
		/* Must also remove ALL (otherwise nothing will happen). */
		new_flags_at_boot &= ~EC_FLASH_PROTECT_ALL_AT_BOOT;
	}
#endif

	/* 1.f - Commit *_AT_BOOT "clears" (and RO "set" 1.b). */
	if (new_flags_at_boot != old_flags_at_boot) {
		rv = flash_protect_at_boot(new_flags_at_boot);
		if (rv)
			retval = rv;
		old_flags_at_boot = new_flags_at_boot;
	}

	/* 2 - Return if RO_AT_BOOT and HW-WP are not asserted.
	 *
	 * All subsequent flags only work if write protect is enabled (that is,
	 * hardware WP flag) *and* RO is protected at boot (software WP flag).
	 */
	if ((~flash_get_protect()) & (EC_FLASH_PROTECT_GPIO_ASSERTED |
				      EC_FLASH_PROTECT_RO_AT_BOOT))
		return retval;

	/*
	 * 3.a - Set ALL_AT_BOOT.
	 *
	 * The case where ALL/RW/ROLLBACK_AT_BOOT is cleared is already covered
	 * above, so we do not need to mask it out.
	 */
	new_flags_at_boot |= flags & EC_FLASH_PROTECT_ALL_AT_BOOT;

	/* 3.b - Set RW_AT_BOOT. */
#ifdef CONFIG_FLASH_PROTECT_RW
	new_flags_at_boot |= flags & EC_FLASH_PROTECT_RW_AT_BOOT;
#endif

	/* 3.c - Set ROLLBACK_AT_BOOT. */
#ifdef CONFIG_ROLLBACK
	new_flags_at_boot |= flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT;
#endif

	/* 3.d - Commit *_AT_BOOT "sets". */
	if (new_flags_at_boot != old_flags_at_boot) {
		rv = flash_protect_at_boot(new_flags_at_boot);
		if (rv)
			retval = rv;
	}

	/* 4 - Commit RO_NOW. */
	if (flags & EC_FLASH_PROTECT_RO_NOW) {
		rv = flash_physical_protect_now(0);
		if (rv)
			retval = rv;
	}

	/* 5 - Commit ALL_NOW. */
	if (flags & EC_FLASH_PROTECT_ALL_NOW) {
		rv = flash_physical_protect_now(1);
		if (rv)
			retval = rv;
	}

	return retval;
}

#ifdef CONFIG_FLASH_DEFERRED_ERASE
static volatile enum ec_status erase_rc = EC_RES_SUCCESS;
static struct ec_params_flash_erase_v1 erase_info;

static void flash_erase_deferred(void)
{
	erase_rc = EC_RES_BUSY;
	if (flash_erase(erase_info.params.offset, erase_info.params.size))
		erase_rc = EC_RES_ERROR;
	else
		erase_rc = EC_RES_SUCCESS;
}
DECLARE_DEFERRED(flash_erase_deferred);
#endif

/*****************************************************************************/
/* Console commands */

#ifdef CONFIG_CMD_FLASHINFO
static int command_flash_info(int argc, char **argv)
{
	int i, flags;

	ccprintf("Usable:  %4d KB\n", CONFIG_FLASH_SIZE / 1024);
	ccprintf("Write:   %4d B (ideal %d B)\n", CONFIG_FLASH_WRITE_SIZE,
		 CONFIG_FLASH_WRITE_IDEAL_SIZE);
#ifdef CONFIG_FLASH_MULTIPLE_REGION
	ccprintf("Regions:\n");
	for (i = 0; i < ARRAY_SIZE(flash_bank_array); i++) {
		ccprintf(" %d region%s:\n",
			 flash_bank_array[i].count,
			 (flash_bank_array[i].count == 1 ? "" : "s"));
		ccprintf("  Erase:   %4d B (to %d-bits)\n",
			 1 << flash_bank_array[i].erase_size_exp,
			 CONFIG_FLASH_ERASED_VALUE32 ? 1 : 0);
		ccprintf("  Size/Protect: %4d B\n",
			 1 << flash_bank_array[i].size_exp);
	}
#else
	ccprintf("Erase:   %4d B (to %d-bits)\n", CONFIG_FLASH_ERASE_SIZE,
		 CONFIG_FLASH_ERASED_VALUE32 ? 1 : 0);
	ccprintf("Protect: %4d B\n", CONFIG_FLASH_BANK_SIZE);
#endif
	flags = flash_get_protect();
	ccprintf("Flags:  ");
	if (flags & EC_FLASH_PROTECT_GPIO_ASSERTED)
		ccputs(" wp_gpio_asserted");
	if (flags & EC_FLASH_PROTECT_RO_AT_BOOT)
		ccputs(" ro_at_boot");
	if (flags & EC_FLASH_PROTECT_ALL_AT_BOOT)
		ccputs(" all_at_boot");
	if (flags & EC_FLASH_PROTECT_RO_NOW)
		ccputs(" ro_now");
	if (flags & EC_FLASH_PROTECT_ALL_NOW)
		ccputs(" all_now");
#ifdef CONFIG_FLASH_PROTECT_RW
	if (flags & EC_FLASH_PROTECT_RW_AT_BOOT)
		ccputs(" rw_at_boot");
	if (flags & EC_FLASH_PROTECT_RW_NOW)
		ccputs(" rw_now");
#endif
	if (flags & EC_FLASH_PROTECT_ERROR_STUCK)
		ccputs(" STUCK");
	if (flags & EC_FLASH_PROTECT_ERROR_INCONSISTENT)
		ccputs(" INCONSISTENT");
#ifdef CONFIG_ROLLBACK
	if (flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT)
		ccputs(" rollback_at_boot");
	if (flags & EC_FLASH_PROTECT_ROLLBACK_NOW)
		ccputs(" rollback_now");
#endif
	ccputs("\n");

	ccputs("Protected now:");
	for (i = 0; i < PHYSICAL_BANKS; i++) {
		if (!(i & 31))
			ccputs("\n    ");
		else if (!(i & 7))
			ccputs(" ");
		ccputs(flash_physical_get_protect(i) ? "Y" : ".");
	}
	ccputs("\n");
	return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(flashinfo, command_flash_info,
			     NULL,
			     "Print flash info");
#endif /* CONFIG_CMD_FLASHINFO */

#ifdef CONFIG_CMD_FLASH
static int command_flash_erase(int argc, char **argv)
{
	int offset = -1;
	int size = -1;
	int rv;

	if (flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW)
		return EC_ERROR_ACCESS_DENIED;

	rv = parse_offset_size(argc, argv, 1, &offset, &size);
	if (rv)
		return rv;

	ccprintf("Erasing %d bytes at 0x%x...\n", size, offset);
	return flash_erase(offset, size);
}
DECLARE_CONSOLE_COMMAND(flasherase, command_flash_erase,
			"offset size",
			"Erase flash");

static int command_flash_write(int argc, char **argv)
{
	int offset = -1;
	int size = -1;
	int rv;
	char *data;
	int i;

	if (flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW)
		return EC_ERROR_ACCESS_DENIED;

	rv = parse_offset_size(argc, argv, 1, &offset, &size);
	if (rv)
		return rv;

	if (size > shared_mem_size())
		size = shared_mem_size();

	/* Acquire the shared memory buffer */
	rv = shared_mem_acquire(size, &data);
	if (rv) {
		ccputs("Can't get shared mem\n");
		return rv;
	}

	/* Fill the data buffer with a pattern */
	for (i = 0; i < size; i++)
		data[i] = i;

	ccprintf("Writing %d bytes to 0x%x...\n", size, offset);
	rv = flash_write(offset, size, data);

	/* Free the buffer */
	shared_mem_release(data);

	return rv;
}
DECLARE_CONSOLE_COMMAND(flashwrite, command_flash_write,
			"offset size",
			"Write pattern to flash");

static int command_flash_read(int argc, char **argv)
{
	int offset = -1;
	int size = 256;
	int rv;
	char *data;
	int i;

	rv = parse_offset_size(argc, argv, 1, &offset, &size);
	if (rv)
		return rv;

	if (size > shared_mem_size())
		size = shared_mem_size();

	/* Acquire the shared memory buffer */
	rv = shared_mem_acquire(size, &data);
	if (rv) {
		ccputs("Can't get shared mem\n");
		return rv;
	}

	/* Read the data */
	if (flash_read(offset, size, data)) {
		shared_mem_release(data);
		return EC_ERROR_INVAL;
	}

	/* Dump it */
	for (i = 0; i < size; i++) {
		if ((offset + i) % 16) {
			ccprintf(" %02x", data[i]);
		} else {
			ccprintf("\n%08x: %02x", offset + i, data[i]);
			cflush();
		}
	}
	ccprintf("\n");

	/* Free the buffer */
	shared_mem_release(data);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(flashread, command_flash_read,
			"offset [size]",
			"Read flash");
#endif

static int command_flash_wp(int argc, char **argv)
{
	int val;

	if (argc < 2)
		return EC_ERROR_PARAM_COUNT;

	if (!strcasecmp(argv[1], "now"))
		return flash_set_protect(EC_FLASH_PROTECT_ALL_NOW, -1);

	if (!strcasecmp(argv[1], "all"))
		return flash_set_protect(EC_FLASH_PROTECT_ALL_AT_BOOT, -1);

	if (!strcasecmp(argv[1], "noall"))
		return flash_set_protect(EC_FLASH_PROTECT_ALL_AT_BOOT, 0);

#ifdef CONFIG_FLASH_PROTECT_RW
	if (!strcasecmp(argv[1], "rw"))
		return flash_set_protect(EC_FLASH_PROTECT_RW_AT_BOOT, -1);

	if (!strcasecmp(argv[1], "norw"))
		return flash_set_protect(EC_FLASH_PROTECT_RW_AT_BOOT, 0);
#endif

#ifdef CONFIG_ROLLBACK
	if (!strcasecmp(argv[1], "rb"))
		return flash_set_protect(EC_FLASH_PROTECT_ROLLBACK_AT_BOOT, -1);

	if (!strcasecmp(argv[1], "norb"))
		return flash_set_protect(EC_FLASH_PROTECT_ROLLBACK_AT_BOOT, 0);
#endif

	/* Do this last, since anything starting with 'n' means "no" */
	if (parse_bool(argv[1], &val))
		return flash_set_protect(EC_FLASH_PROTECT_RO_AT_BOOT,
					 val ? -1 : 0);

	return EC_ERROR_PARAM1;
}
DECLARE_CONSOLE_COMMAND(flashwp, command_flash_wp,
			"<BOOLEAN> | now | all | noall"
#ifdef CONFIG_FLASH_PROTECT_RW
			" | rw | norw"
#endif
#ifdef CONFIG_ROLLBACK
			" | rb | norb"
#endif
			, "Modify flash write protect");

/*****************************************************************************/
/* Host commands */

/*
 * All internal EC code assumes that offsets are provided relative to
 * physical address zero of storage. In some cases, the region of storage
 * belonging to the EC is not physical address zero - a non-zero fmap_base
 * indicates so. Since fmap_base is not yet handled correctly by external
 * code, we must perform the adjustment in our host command handlers -
 * adjust all offsets so they are relative to the beginning of the storage
 * region belonging to the EC. TODO(crbug.com/529365): Handle fmap_base
 * correctly in flashrom, dump_fmap, etc. and remove EC_FLASH_REGION_START.
 */
#define EC_FLASH_REGION_START MIN(CONFIG_EC_PROTECTED_STORAGE_OFF, \
				  CONFIG_EC_WRITABLE_STORAGE_OFF)

static enum ec_status flash_command_get_info(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_info_2 *p_2 = args->params;
	struct ec_response_flash_info_2 *r_2 = args->response;
#ifdef CONFIG_FLASH_MULTIPLE_REGION
	int banks_size = ARRAY_SIZE(flash_bank_array);
	const struct ec_flash_bank *banks = flash_bank_array;
#else
	struct ec_response_flash_info_1 *r_1 = args->response;
#if CONFIG_FLASH_BANK_SIZE < CONFIG_FLASH_ERASE_SIZE
#error "Flash: Bank size expected bigger or equal to erase size."
#endif
	struct ec_flash_bank single_bank = {
		.count = CONFIG_FLASH_SIZE / CONFIG_FLASH_BANK_SIZE,
		.size_exp = __fls(CONFIG_FLASH_BANK_SIZE),
		.write_size_exp = __fls(CONFIG_FLASH_WRITE_SIZE),
		.erase_size_exp = __fls(CONFIG_FLASH_ERASE_SIZE),
		.protect_size_exp = __fls(CONFIG_FLASH_BANK_SIZE),
	};
	int banks_size = 1;
	const struct ec_flash_bank *banks = &single_bank;
#endif
	int banks_len;
	int ideal_size;

	/*
	 * Compute the ideal amount of data for the host to send us,
	 * based on the maximum response size and the ideal write size.
	 */
	ideal_size = (args->response_max -
		 sizeof(struct ec_params_flash_write)) &
		~(CONFIG_FLASH_WRITE_IDEAL_SIZE - 1);
	/*
	 * If we can't get at least one ideal block, then just want
	 * as high a multiple of the minimum write size as possible.
	 */
	if (!ideal_size)
		ideal_size = (args->response_max -
				sizeof(struct ec_params_flash_write)) &
				~(CONFIG_FLASH_WRITE_SIZE - 1);


	if (args->version >= 2) {
		args->response_size = sizeof(struct ec_response_flash_info_2);
		r_2->flash_size = CONFIG_FLASH_SIZE - EC_FLASH_REGION_START;
#if (CONFIG_FLASH_ERASED_VALUE32 == 0)
		r_2->flags = EC_FLASH_INFO_ERASE_TO_0;
#else
		r_2->flags = 0;
#endif
#ifdef CONFIG_FLASH_SELECT_REQUIRED
		r_2->flags |= EC_FLASH_INFO_SELECT_REQUIRED;
#endif
		r_2->write_ideal_size = ideal_size;
		r_2->num_banks_total = banks_size;
		r_2->num_banks_desc = MIN(banks_size, p_2->num_banks_desc);
		banks_len = r_2->num_banks_desc * sizeof(struct ec_flash_bank);
		memcpy(r_2->banks, banks, banks_len);
		args->response_size += banks_len;
		return EC_RES_SUCCESS;
	}
#ifdef CONFIG_FLASH_MULTIPLE_REGION
	return EC_RES_INVALID_PARAM;
#else
	r_1->flash_size = CONFIG_FLASH_SIZE - EC_FLASH_REGION_START;
	r_1->flags = 0;
	r_1->write_block_size = CONFIG_FLASH_WRITE_SIZE;
	r_1->erase_block_size = CONFIG_FLASH_ERASE_SIZE;
	r_1->protect_block_size = CONFIG_FLASH_BANK_SIZE;
	if (args->version == 0) {
		/* Only version 0 fields returned */
		args->response_size = sizeof(struct ec_response_flash_info);
	} else {
		args->response_size = sizeof(struct ec_response_flash_info_1);
		/* Fill in full version 1 struct */
		r_1->write_ideal_size = ideal_size;
#if (CONFIG_FLASH_ERASED_VALUE32 == 0)
		r_1->flags |= EC_FLASH_INFO_ERASE_TO_0;
#endif
#ifdef CONFIG_FLASH_SELECT_REQUIRED
		r_1->flags |= EC_FLASH_INFO_SELECT_REQUIRED;
#endif
	}
	return EC_RES_SUCCESS;
#endif  /* CONFIG_FLASH_MULTIPLE_REGION */
}
#ifdef CONFIG_FLASH_MULTIPLE_REGION
#define FLASH_INFO_VER EC_VER_MASK(2)
#else
#define FLASH_INFO_VER (EC_VER_MASK(0) | EC_VER_MASK(1) | EC_VER_MASK(2))
#endif
DECLARE_HOST_COMMAND(EC_CMD_FLASH_INFO,
		     flash_command_get_info, FLASH_INFO_VER);


static enum ec_status flash_command_read(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_read *p = args->params;
	uint32_t offset = p->offset + EC_FLASH_REGION_START;

	if (p->size > args->response_max)
		return EC_RES_OVERFLOW;

	if (flash_read(offset, p->size, args->response))
		return EC_RES_ERROR;

	args->response_size = p->size;

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FLASH_READ,
		     flash_command_read,
		     EC_VER_MASK(0));

/**
 * Flash write command
 *
 * Version 0 and 1 are equivalent from the EC-side; the only difference is
 * that the host can only send 64 bytes of data at a time in version 0.
 */
static enum ec_status flash_command_write(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_write *p = args->params;
	uint32_t offset = p->offset + EC_FLASH_REGION_START;

	if (flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW)
		return EC_RES_ACCESS_DENIED;

	if (p->size + sizeof(*p) > args->params_size)
		return EC_RES_INVALID_PARAM;

#ifdef CONFIG_INTERNAL_STORAGE
	if (system_unsafe_to_overwrite(offset, p->size))
		return EC_RES_ACCESS_DENIED;
#endif

	if (flash_write(offset, p->size, (const uint8_t *)(p + 1)))
		return EC_RES_ERROR;

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FLASH_WRITE,
		     flash_command_write,
		     EC_VER_MASK(0) | EC_VER_MASK(EC_VER_FLASH_WRITE));

#ifndef CONFIG_FLASH_MULTIPLE_REGION
/*
 * Make sure our image sizes are a multiple of flash block erase size so that
 * the host can erase the entire image.
 */
BUILD_ASSERT(CONFIG_RO_SIZE % CONFIG_FLASH_ERASE_SIZE == 0);
BUILD_ASSERT(CONFIG_RW_SIZE % CONFIG_FLASH_ERASE_SIZE == 0);
BUILD_ASSERT(EC_FLASH_REGION_RO_SIZE % CONFIG_FLASH_ERASE_SIZE == 0);
BUILD_ASSERT(CONFIG_EC_WRITABLE_STORAGE_SIZE % CONFIG_FLASH_ERASE_SIZE == 0);

#endif

static enum ec_status flash_command_erase(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_erase *p = args->params;
	int rc = EC_RES_SUCCESS, cmd = FLASH_ERASE_SECTOR;
	uint32_t offset;
#ifdef CONFIG_FLASH_DEFERRED_ERASE
	const struct ec_params_flash_erase_v1 *p_1 = args->params;

	if (args->version > 0) {
		cmd = p_1->cmd;
		p = &p_1->params;
	}
#endif
	offset = p->offset + EC_FLASH_REGION_START;

	if (flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW)
		return EC_RES_ACCESS_DENIED;

#ifdef CONFIG_INTERNAL_STORAGE
	if (system_unsafe_to_overwrite(offset, p->size))
		return EC_RES_ACCESS_DENIED;
#endif

	switch (cmd) {
	case FLASH_ERASE_SECTOR:
#if defined(HAS_TASK_HOSTCMD) && defined(CONFIG_HOST_COMMAND_STATUS)
		args->result = EC_RES_IN_PROGRESS;
		host_send_response(args);
#endif
		if (flash_erase(offset, p->size))
			return EC_RES_ERROR;

		break;
#ifdef CONFIG_FLASH_DEFERRED_ERASE
	case FLASH_ERASE_SECTOR_ASYNC:
		rc = erase_rc;
		if (rc == EC_RES_SUCCESS) {
			memcpy(&erase_info, p_1, sizeof(*p_1));
			hook_call_deferred(&flash_erase_deferred_data,
					   100 * MSEC);
		} else {
			/*
			 * Not our job to return the result of
			 * the previous command.
			 */
			rc = EC_RES_BUSY;
		}
		break;
	case FLASH_ERASE_GET_RESULT:
		rc = erase_rc;
		if (rc != EC_RES_BUSY)
			/* Ready for another command */
			erase_rc = EC_RES_SUCCESS;
		break;
#endif
	default:
		rc = EC_RES_INVALID_PARAM;
	}
	return rc;
}


DECLARE_HOST_COMMAND(EC_CMD_FLASH_ERASE, flash_command_erase,
		EC_VER_MASK(0)
#ifdef CONFIG_FLASH_DEFERRED_ERASE
		| EC_VER_MASK(1)
#endif
		);

static enum ec_status flash_command_protect(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_protect *p = args->params;
	struct ec_response_flash_protect *r = args->response;

	/*
	 * Handle requesting new flags.  Note that we ignore the return code
	 * from flash_set_protect(), since errors will be visible to the caller
	 * via the flags in the response.  (If we returned error, the caller
	 * wouldn't get the response.)
	 */
	if (p->mask)
		flash_set_protect(p->mask, p->flags);

	/*
	 * Retrieve the current flags.  The caller can use this to determine
	 * which of the requested flags could be set.  This is cleaner than
	 * simply returning error, because it provides information to the
	 * caller about the actual result.
	 */
	r->flags = flash_get_protect();

	/* Indicate which flags are valid on this platform */
	r->valid_flags =
		EC_FLASH_PROTECT_GPIO_ASSERTED |
		EC_FLASH_PROTECT_ERROR_STUCK |
		EC_FLASH_PROTECT_ERROR_INCONSISTENT |
		flash_physical_get_valid_flags();
	r->writable_flags = flash_physical_get_writable_flags(r->flags);

	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}

/*
 * TODO(crbug.com/239197) : Adding both versions to the version mask is a
 * temporary workaround for a problem in the cros_ec driver. Drop
 * EC_VER_MASK(0) once cros_ec driver can send the correct version.
 */
DECLARE_HOST_COMMAND(EC_CMD_FLASH_PROTECT,
		     flash_command_protect,
		     EC_VER_MASK(0) | EC_VER_MASK(1));

static enum ec_status
flash_command_region_info(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_region_info *p = args->params;
	struct ec_response_flash_region_info *r = args->response;

	switch (p->region) {
	case EC_FLASH_REGION_RO:
		r->offset = CONFIG_EC_PROTECTED_STORAGE_OFF +
			    CONFIG_RO_STORAGE_OFF -
			    EC_FLASH_REGION_START;
		r->size = EC_FLASH_REGION_RO_SIZE;
		break;
	case EC_FLASH_REGION_ACTIVE:
		r->offset = flash_get_rw_offset(system_get_active_copy()) -
				EC_FLASH_REGION_START;
		r->size = CONFIG_EC_WRITABLE_STORAGE_SIZE;
		break;
	case EC_FLASH_REGION_WP_RO:
		r->offset = CONFIG_WP_STORAGE_OFF -
			    EC_FLASH_REGION_START;
		r->size = CONFIG_WP_STORAGE_SIZE;
		break;
	case EC_FLASH_REGION_UPDATE:
		r->offset = flash_get_rw_offset(system_get_update_copy()) -
				EC_FLASH_REGION_START;
		r->size = CONFIG_EC_WRITABLE_STORAGE_SIZE;
		break;
	default:
		return EC_RES_INVALID_PARAM;
	}

	args->response_size = sizeof(*r);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FLASH_REGION_INFO,
		     flash_command_region_info,
		     EC_VER_MASK(EC_VER_FLASH_REGION_INFO));


#ifdef CONFIG_FLASH_SELECT_REQUIRED

static enum ec_status flash_command_select(struct host_cmd_handler_args *args)
{
	const struct ec_params_flash_select *p = args->params;

	return board_flash_select(p->select);
}
DECLARE_HOST_COMMAND(EC_CMD_FLASH_SELECT,
		     flash_command_select,
		     EC_VER_MASK(0));

#endif /* CONFIG_FLASH_SELECT_REQUIRED */
