1428 lines
38 KiB
C
1428 lines
38 KiB
C
|
/* 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)
|
||
|
|
||
|
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. */
|
||
|
#else
|
||
|
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
|
||
|
|
||
|
#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
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#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;
|
||
|
}
|
||
|
#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
|
||
|
/**
|
||
|
* Write persistent serial number to pstate, erasing if necessary.
|
||
|
*
|
||
|
* @param serialno New iascii serial number to set in pstate.
|
||
|
* @return EC_SUCCESS, or nonzero if error.
|
||
|
*/
|
||
|
int flash_write_pstate_serial(const char *serialno)
|
||
|
{
|
||
|
int i;
|
||
|
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;
|
||
|
|
||
|
/* Cache the old copy for read/modify/write. */
|
||
|
memcpy(&newpstate, pstate, sizeof(newpstate));
|
||
|
validate_pstate_struct(&newpstate);
|
||
|
|
||
|
/* Copy in serialno. */
|
||
|
for (i = 0; i < CONFIG_SERIALNO_LEN - 1; i++) {
|
||
|
newpstate.serialno[i] = serialno[i];
|
||
|
if (serialno[i] == 0)
|
||
|
break;
|
||
|
}
|
||
|
for (; i < CONFIG_SERIALNO_LEN; i++)
|
||
|
newpstate.serialno[i] = 0;
|
||
|
newpstate.valid_fields |= PSTATE_VALID_SERIALNO;
|
||
|
|
||
|
return flash_write_pstate_data(&newpstate);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#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();
|
||
|
}
|
||
|
|
||
|
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:
|
||
|
* 1. RO_AT_BOOT was already set before flash_set_protect() is
|
||
|
* called.
|
||
|
* 2. RO_AT_BOOT was not set, but it's requested to be set by
|
||
|
* the caller of flash_set_protect().
|
||
|
*/
|
||
|
|
||
|
new_flags_at_boot &= ~(mask & EC_FLASH_PROTECT_RO_AT_BOOT);
|
||
|
new_flags_at_boot |= flags & EC_FLASH_PROTECT_RO_AT_BOOT;
|
||
|
|
||
|
/* Removing ALL must also remove RW/ROLLBACK */
|
||
|
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;
|
||
|
#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
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_FLASH_PROTECT_RW
|
||
|
/* Removing RW must also remove ALL (otherwise nothing will happen). */
|
||
|
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;
|
||
|
new_flags_at_boot &= ~EC_FLASH_PROTECT_ALL_AT_BOOT;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#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;
|
||
|
new_flags_at_boot &= ~EC_FLASH_PROTECT_ALL_AT_BOOT;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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;
|
||
|
|
||
|
/*
|
||
|
* The case where ALL/RW/ROLLBACK_AT_BOOT is unset is already covered
|
||
|
* above, so we do not need to mask it out.
|
||
|
*/
|
||
|
new_flags_at_boot |= flags & EC_FLASH_PROTECT_ALL_AT_BOOT;
|
||
|
|
||
|
#ifdef CONFIG_FLASH_PROTECT_RW
|
||
|
new_flags_at_boot |= flags & EC_FLASH_PROTECT_RW_AT_BOOT;
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_ROLLBACK
|
||
|
new_flags_at_boot |= flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT;
|
||
|
#endif
|
||
|
|
||
|
if (new_flags_at_boot != old_flags_at_boot) {
|
||
|
rv = flash_protect_at_boot(new_flags_at_boot);
|
||
|
if (rv)
|
||
|
retval = rv;
|
||
|
}
|
||
|
|
||
|
if (flags & EC_FLASH_PROTECT_RO_NOW) {
|
||
|
rv = flash_physical_protect_now(0);
|
||
|
if (rv)
|
||
|
retval = rv;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
#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 = CONFIG_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_RW_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_RW_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 */
|