mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-31 11:01:19 +00:00
Flash operations in do_flash_op() involve waiting polling for the chip
to complete the operation. If a concurrent operation is started while
another operation is in progress, flash gets confused and locks up.
Let's add a mutex to ensure that flash operation runs to completion
before another operation starts.
BRANCH=cr50
BUG=b:67651754
TEST=multiple times ran firmware update while the device was coming up
and saving TPM status in NVMEM. Observed no failures.
Change-Id: I777a38f8a63cf17d60edb11cc3f916a4ea904741
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/894180
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
548 lines
14 KiB
C
548 lines
14 KiB
C
/* Copyright 2015 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
/*
|
|
* The SoC's internal flash consists of two separate "banks" of 256K bytes each
|
|
* (sometimes called "macros" because of how they're implemented in Verilog).
|
|
*
|
|
* Each flash bank contains 128 "blocks" or "pages" of 2K bytes each. These
|
|
* blocks can be erased individually, or the entire bank can be erased at once.
|
|
*
|
|
* When the flash content is erased, all its bits are set to 1.
|
|
*
|
|
* The flash content can be read directly as bytes, halfwords, or words, just
|
|
* like any memory region. However, writes can only happen through special
|
|
* operations, in units of properly aligned 32-bit words.
|
|
*
|
|
* The flash controller has a 32-word write buffer. This allows up to 32
|
|
* adjacent words (128 bytes) within a bank to be written in one operation.
|
|
*
|
|
* Multiple writes to the same flash word can be done without first erasing the
|
|
* block, however:
|
|
*
|
|
* A) writes can only change stored bits from 1 to 0, and
|
|
*
|
|
* B) the manufacturer recommends that no more than two writes be done between
|
|
* erase cycles for best results (in terms of reliability, longevity, etc.)
|
|
*
|
|
* All of this is fairly typical of most flash parts. This next thing is NOT
|
|
* typical:
|
|
*
|
|
* +--------------------------------------------------------------------------+
|
|
* + While any write or erase operation is in progress, ALL other access to +
|
|
* + that entire bank is stalled. Data reads, instruction fetches, interrupt +
|
|
* + vector lookup -- every access blocks until the flash operation finishes. +
|
|
* +--------------------------------------------------------------------------+
|
|
*
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "cryptoc/util.h"
|
|
#include "flash.h"
|
|
#include "flash_config.h"
|
|
#include "flash_info.h"
|
|
#include "registers.h"
|
|
#include "shared_mem.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "watchdog.h"
|
|
|
|
#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args)
|
|
|
|
/* Mutex to prevent concurrent accesses to flash engine. */
|
|
static struct mutex flash_mtx;
|
|
|
|
int flash_pre_init(void)
|
|
{
|
|
struct g_flash_region regions[4];
|
|
int i, num_regions;
|
|
|
|
num_regions = flash_regions_to_enable(regions, ARRAY_SIZE(regions));
|
|
|
|
for (i = 0; i < num_regions; i++) {
|
|
int reg_base;
|
|
|
|
/* Region range */
|
|
reg_base = GBASE(GLOBALSEC) +
|
|
GOFFSET(GLOBALSEC, FLASH_REGION2_BASE_ADDR) +
|
|
i * 8;
|
|
|
|
REG32(reg_base) = regions[i].reg_base;
|
|
|
|
/*
|
|
* The hardware requires a value which is 1 less than the
|
|
* actual region size.
|
|
*/
|
|
REG32(reg_base + 4) = regions[i].reg_size - 1;
|
|
|
|
/* Region permissions. */
|
|
reg_base = GBASE(GLOBALSEC) +
|
|
GOFFSET(GLOBALSEC, FLASH_REGION2_CTRL) +
|
|
i * 4;
|
|
REG32(reg_base) = regions[i].reg_perms;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int flash_physical_get_protect(int bank)
|
|
{
|
|
return 0; /* Not protected */
|
|
}
|
|
|
|
uint32_t flash_physical_get_protect_flags(void)
|
|
{
|
|
return 0; /* no flags set */
|
|
}
|
|
|
|
uint32_t flash_physical_get_valid_flags(void)
|
|
{
|
|
/* These are the flags we're going to pay attention to */
|
|
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)
|
|
{
|
|
return 0; /* no flags writable */
|
|
}
|
|
|
|
int flash_physical_protect_at_boot(uint32_t new_flags)
|
|
{
|
|
return EC_SUCCESS; /* yeah, I did it. */
|
|
}
|
|
|
|
int flash_physical_protect_now(int all)
|
|
{
|
|
return EC_SUCCESS; /* yeah, I did it. */
|
|
}
|
|
|
|
|
|
enum flash_op {
|
|
OP_ERASE_BLOCK,
|
|
OP_WRITE_BLOCK,
|
|
OP_READ_BLOCK,
|
|
};
|
|
|
|
static int do_flash_op(enum flash_op op, int is_info_bank,
|
|
int byte_offset, int words)
|
|
{
|
|
volatile uint32_t *fsh_pe_control;
|
|
uint32_t opcode, tmp, errors;
|
|
int retry_count, max_attempts, extra_prog_pulse, i;
|
|
int timedelay_us = 100;
|
|
uint32_t prev_error = 0;
|
|
|
|
/* Make sure the smart program/erase algorithms are enabled. */
|
|
if (!GREAD(FLASH, FSH_TIMING_PROG_SMART_ALGO_ON) ||
|
|
!GREAD(FLASH, FSH_TIMING_ERASE_SMART_ALGO_ON)) {
|
|
CPRINTF("%s:%d\n", __func__, __LINE__);
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
/* Error status is self-clearing. Read it until it does (we hope). */
|
|
for (i = 0; i < 50; i++) {
|
|
tmp = GREAD(FLASH, FSH_ERROR);
|
|
if (!tmp)
|
|
break;
|
|
usleep(timedelay_us);
|
|
}
|
|
/* If we can't clear the error status register then something is wrong.
|
|
*/
|
|
if (tmp) {
|
|
CPRINTF("%s:%d\n", __func__, __LINE__);
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* We have two flash banks. Adjust offset and registers accordingly. */
|
|
if (is_info_bank) {
|
|
/* Only INFO bank operations are supported. */
|
|
fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL1);
|
|
} else if (byte_offset >= CFG_FLASH_HALF) {
|
|
byte_offset -= CFG_FLASH_HALF;
|
|
fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL1);
|
|
} else {
|
|
fsh_pe_control = GREG32_ADDR(FLASH, FSH_PE_CONTROL0);
|
|
}
|
|
|
|
/* What are we doing? */
|
|
switch (op) {
|
|
case OP_ERASE_BLOCK:
|
|
#ifndef CR50_DEV
|
|
if (is_info_bank)
|
|
/* Erasing the INFO bank from the RW section is
|
|
* unsupported. */
|
|
return EC_ERROR_INVAL;
|
|
#endif
|
|
opcode = 0x31415927;
|
|
words = 0; /* don't care, really */
|
|
/* This number is based on the TSMC spec Nme=Terase/Tsme */
|
|
max_attempts = 45;
|
|
break;
|
|
case OP_WRITE_BLOCK:
|
|
opcode = 0x27182818;
|
|
words--; /* count register is zero-based */
|
|
/* This number is based on the TSMC spec Nmp=Tprog/Tsmp */
|
|
max_attempts = 9;
|
|
break;
|
|
case OP_READ_BLOCK:
|
|
if (!is_info_bank)
|
|
/* This code path only supports reading from
|
|
* the INFO bank.
|
|
*/
|
|
return EC_ERROR_INVAL;
|
|
opcode = 0x16021765;
|
|
words = 1;
|
|
max_attempts = 9;
|
|
break;
|
|
default:
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
/*
|
|
* Set the parameters. For writes, we assume the write buffer is
|
|
* already filled before we call this function.
|
|
*/
|
|
GWRITE_FIELD(FLASH, FSH_TRANS, OFFSET,
|
|
byte_offset / 4); /* word offset */
|
|
GWRITE_FIELD(FLASH, FSH_TRANS, MAINB, is_info_bank ? 1 : 0);
|
|
GWRITE_FIELD(FLASH, FSH_TRANS, SIZE, words);
|
|
|
|
/* TODO: Make sure this function isn't getting called "too often" in
|
|
* between erases.
|
|
*/
|
|
extra_prog_pulse = 0;
|
|
for (retry_count = 0; retry_count < max_attempts; retry_count++) {
|
|
/* Kick it off */
|
|
GWRITE(FLASH, FSH_PE_EN, 0xb11924e1);
|
|
*fsh_pe_control = opcode;
|
|
|
|
/* Wait for completion. 150ms should be enough
|
|
* (crosbug.com/p/45366).
|
|
*/
|
|
for (i = 0; i < 1500; i++) {
|
|
tmp = *fsh_pe_control;
|
|
if (!tmp)
|
|
break;
|
|
usleep(timedelay_us);
|
|
}
|
|
|
|
/* Timed out waiting for control register to clear */
|
|
if (tmp) {
|
|
CPRINTF("%s:%d\n", __func__, __LINE__);
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
/* Check error status */
|
|
errors = GREAD(FLASH, FSH_ERROR);
|
|
|
|
if (errors && (errors != prev_error)) {
|
|
prev_error = errors;
|
|
CPRINTF("%s:%d errors %x fsh_pe_control %p\n",
|
|
__func__, __LINE__, errors, fsh_pe_control);
|
|
}
|
|
/* Error status is self-clearing. Read it until it does
|
|
* (we hope).
|
|
*/
|
|
for (i = 0; i < 50; i++) {
|
|
tmp = GREAD(FLASH, FSH_ERROR);
|
|
if (!tmp)
|
|
break;
|
|
usleep(timedelay_us);
|
|
}
|
|
/* If we can't clear the error status register then something
|
|
* is wrong.
|
|
*/
|
|
if (tmp) {
|
|
CPRINTF("%s:%d\n", __func__, __LINE__);
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
/* The operation was successful. */
|
|
if (!errors) {
|
|
/* From the spec:
|
|
* "In addition, one more program pulse is needed after
|
|
* program verification is passed."
|
|
*/
|
|
if (op == OP_WRITE_BLOCK && !extra_prog_pulse) {
|
|
extra_prog_pulse = 1;
|
|
max_attempts++;
|
|
continue;
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
/* If there were errors after completion retry. */
|
|
watchdog_reload();
|
|
}
|
|
CPRINTF("%s:%d, retry count %d\n", __func__, __LINE__, retry_count);
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Write up to CONFIG_FLASH_WRITE_IDEAL_SIZE bytes at once */
|
|
static int write_batch(int byte_offset, int is_info_bank,
|
|
int words, const uint8_t *data)
|
|
{
|
|
volatile uint32_t *fsh_wr_data = GREG32_ADDR(FLASH, FSH_WR_DATA0);
|
|
uint32_t val;
|
|
int i;
|
|
int rv;
|
|
|
|
mutex_lock(&flash_mtx);
|
|
|
|
/* Load the write buffer. */
|
|
for (i = 0; i < words; i++) {
|
|
/*
|
|
* We have to write 32-bit values, but we can't guarantee
|
|
* alignment for the data. We'll just assemble the word
|
|
* manually to avoid alignment faults. Note that we're assuming
|
|
* little-endian order here.
|
|
*/
|
|
val = ((data[3] << 24) | (data[2] << 16) |
|
|
(data[1] << 8) | data[0]);
|
|
|
|
*fsh_wr_data = val;
|
|
data += 4;
|
|
fsh_wr_data++;
|
|
}
|
|
|
|
rv = do_flash_op(OP_WRITE_BLOCK, is_info_bank, byte_offset, words);
|
|
|
|
mutex_unlock(&flash_mtx);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int flash_physical_write_internal(int byte_offset, int is_info_bank,
|
|
int num_bytes, const char *data)
|
|
{
|
|
int num, ret;
|
|
|
|
/* The offset and size must be a multiple of CONFIG_FLASH_WRITE_SIZE */
|
|
if (byte_offset % CONFIG_FLASH_WRITE_SIZE ||
|
|
num_bytes % CONFIG_FLASH_WRITE_SIZE)
|
|
return EC_ERROR_INVAL;
|
|
|
|
while (num_bytes) {
|
|
num = MIN(num_bytes, CONFIG_FLASH_WRITE_IDEAL_SIZE);
|
|
/*
|
|
* Make sure that the write operation will not go
|
|
* past a CONFIG_FLASH_ROW_SIZE boundary.
|
|
*/
|
|
num = MIN(num, CONFIG_FLASH_ROW_SIZE -
|
|
byte_offset % CONFIG_FLASH_ROW_SIZE);
|
|
ret = write_batch(byte_offset,
|
|
is_info_bank,
|
|
num / 4, /* word count */
|
|
(const uint8_t *)data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
num_bytes -= num;
|
|
byte_offset += num;
|
|
data += num;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int flash_physical_write(int byte_offset, int num_bytes, const char *data)
|
|
{
|
|
return flash_physical_write_internal(byte_offset, 0, num_bytes, data);
|
|
}
|
|
|
|
int flash_physical_info_read_word(int byte_offset, uint32_t *dst)
|
|
{
|
|
int ret;
|
|
|
|
if (byte_offset % CONFIG_FLASH_WRITE_SIZE)
|
|
return EC_ERROR_INVAL;
|
|
|
|
mutex_lock(&flash_mtx);
|
|
|
|
ret = do_flash_op(OP_READ_BLOCK, 1, byte_offset, 1);
|
|
if (ret == EC_SUCCESS)
|
|
*dst = GREG32(FLASH, FSH_DOUT_VAL1);
|
|
|
|
mutex_unlock(&flash_mtx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Verify that the range's size is power of 2, the range offset is aligned by
|
|
* size, and the range does not cross the INFO space boundary.
|
|
*/
|
|
static int valid_info_range(uint32_t offset, size_t size)
|
|
{
|
|
if (!size || (size & (size - 1)))
|
|
return 0;
|
|
|
|
if (offset & (size - 1))
|
|
return 0;
|
|
|
|
if ((offset + size) > FLASH_INFO_SIZE)
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
/* Write access is a superset of read access. */
|
|
static int flash_info_configure_access(uint32_t offset,
|
|
size_t size, int write_mode)
|
|
{
|
|
int mask;
|
|
|
|
if (!valid_info_range(offset, size))
|
|
return EC_ERROR_INVAL;
|
|
|
|
mask = GREG32(GLOBALSEC, FLASH_REGION6_CTRL);
|
|
mask |= GC_GLOBALSEC_FLASH_REGION6_CTRL_EN_MASK |
|
|
GC_GLOBALSEC_FLASH_REGION6_CTRL_RD_EN_MASK;
|
|
if (write_mode)
|
|
mask |= GC_GLOBALSEC_FLASH_REGION6_CTRL_WR_EN_MASK;
|
|
|
|
GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) =
|
|
FLASH_INFO_MEMORY_BASE + offset;
|
|
|
|
GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = size - 1;
|
|
GREG32(GLOBALSEC, FLASH_REGION6_CTRL) = mask;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int flash_info_read_enable(uint32_t offset, size_t size)
|
|
{
|
|
return flash_info_configure_access(offset, size, 0);
|
|
}
|
|
|
|
int flash_info_write_enable(uint32_t offset, size_t size)
|
|
{
|
|
return flash_info_configure_access(offset, size, 1);
|
|
}
|
|
|
|
void flash_info_write_disable(void)
|
|
{
|
|
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, WR_EN, 0);
|
|
}
|
|
|
|
int flash_info_physical_write(int byte_offset, int num_bytes, const char *data)
|
|
{
|
|
if (byte_offset < 0 || num_bytes < 0 ||
|
|
byte_offset + num_bytes > FLASH_INFO_SIZE ||
|
|
(byte_offset | num_bytes) & (CONFIG_FLASH_WRITE_SIZE - 1))
|
|
return EC_ERROR_INVAL;
|
|
|
|
return flash_physical_write_internal(byte_offset, 1, num_bytes, data);
|
|
}
|
|
|
|
int flash_physical_erase(int byte_offset, int num_bytes)
|
|
{
|
|
int ret;
|
|
|
|
/* Offset and size must be a multiple of CONFIG_FLASH_ERASE_SIZE */
|
|
if (byte_offset % CONFIG_FLASH_ERASE_SIZE ||
|
|
num_bytes % CONFIG_FLASH_ERASE_SIZE)
|
|
return EC_ERROR_INVAL;
|
|
|
|
while (num_bytes) {
|
|
|
|
mutex_lock(&flash_mtx);
|
|
|
|
/* We may be asked to erase multiple banks */
|
|
ret = do_flash_op(OP_ERASE_BLOCK,
|
|
0, /* not the INFO bank */
|
|
byte_offset,
|
|
num_bytes / 4); /* word count */
|
|
|
|
mutex_unlock(&flash_mtx);
|
|
|
|
if (ret) {
|
|
CPRINTF("Failed to erase block at %x\n", byte_offset);
|
|
return ret;
|
|
}
|
|
|
|
num_bytes -= CONFIG_FLASH_ERASE_SIZE;
|
|
byte_offset += CONFIG_FLASH_ERASE_SIZE;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Enable write access to the backup RO section. */
|
|
void flash_open_ro_window(uint32_t offset, size_t size_b)
|
|
{
|
|
GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) =
|
|
offset + CONFIG_PROGRAM_MEMORY_BASE;
|
|
GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = size_b - 1;
|
|
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, EN, 1);
|
|
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, RD_EN, 1);
|
|
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, WR_EN, 1);
|
|
}
|
|
|
|
#ifdef CR50_DEV
|
|
|
|
static int command_erase_flash_info(int argc, char **argv)
|
|
{
|
|
uint32_t *preserved_manufacture_state;
|
|
const size_t manuf_word_count = FLASH_INFO_MANUFACTURE_STATE_SIZE /
|
|
sizeof(uint32_t);
|
|
int i;
|
|
int rv = EC_ERROR_BUSY;
|
|
|
|
if (shared_mem_acquire(FLASH_INFO_MANUFACTURE_STATE_SIZE,
|
|
(char **)&preserved_manufacture_state) !=
|
|
EC_SUCCESS) {
|
|
ccprintf("Failed to allocate memory for manufacture state!\n");
|
|
return rv;
|
|
}
|
|
|
|
flash_info_read_enable(0, 2048);
|
|
flash_info_write_enable(0, 2048);
|
|
|
|
/* Preserve manufacturing information. */
|
|
for (i = 0; i < manuf_word_count; i++) {
|
|
if (flash_physical_info_read_word
|
|
(FLASH_INFO_MANUFACTURE_STATE_OFFSET +
|
|
i * sizeof(uint32_t),
|
|
preserved_manufacture_state + i) != EC_SUCCESS) {
|
|
ccprintf("Failed to read word %d!\n", i);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&flash_mtx);
|
|
|
|
rv = do_flash_op(OP_ERASE_BLOCK, 1, 0, 512);
|
|
|
|
mutex_unlock(&flash_mtx);
|
|
|
|
if (rv != EC_SUCCESS) {
|
|
ccprintf("Failed to erase info space!\n");
|
|
goto exit;
|
|
}
|
|
|
|
if (flash_info_physical_write
|
|
(FLASH_INFO_MANUFACTURE_STATE_OFFSET,
|
|
FLASH_INFO_MANUFACTURE_STATE_SIZE,
|
|
(char *)preserved_manufacture_state) != EC_SUCCESS) {
|
|
ccprintf("Failed to restore manufacture state!\n");
|
|
goto exit;
|
|
}
|
|
|
|
rv = EC_SUCCESS;
|
|
exit:
|
|
always_memset(preserved_manufacture_state, 0,
|
|
FLASH_INFO_MANUFACTURE_STATE_SIZE);
|
|
shared_mem_release(preserved_manufacture_state);
|
|
flash_info_write_disable();
|
|
return rv;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(eraseflashinfo, command_erase_flash_info,
|
|
"",
|
|
"Erase INFO1 flash space");
|
|
#endif
|