| /* Copyright 2016 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 stm32f4 */ |
| |
| #include "clock.h" |
| #include "compile_time_macros.h" |
| #include "console.h" |
| #include "common.h" |
| #include "flash.h" |
| #include "hooks.h" |
| #include "registers.h" |
| #include "system.h" |
| #include "panic.h" |
| #include "watchdog.h" |
| |
| |
| #define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args) |
| |
| /* |
| * Approximate number of CPU cycles per iteration of the loop when polling |
| * the flash status |
| */ |
| #define CYCLE_PER_FLASH_LOOP 10 |
| |
| /* Flash page programming timeout. This is 2x the datasheet max. */ |
| #define FLASH_TIMEOUT_US 16000 |
| |
| static inline int calculate_flash_timeout(void) |
| { |
| return (FLASH_TIMEOUT_US * |
| (clock_get_freq() / SECOND) / CYCLE_PER_FLASH_LOOP); |
| } |
| |
| |
| /* Flag indicating whether we have locked down entire flash */ |
| static int entire_flash_locked; |
| |
| #define FLASH_SYSJUMP_TAG 0x5750 /* "WP" - Write Protect */ |
| #define FLASH_HOOK_VERSION 1 |
| /* The previous write protect state before sys jump */ |
| struct flash_wp_state { |
| int entire_flash_locked; |
| }; |
| |
| /*****************************************************************************/ |
| /* Physical layer APIs */ |
| |
| /* Flash unlocking keys */ |
| #define PRG_LOCK 0 |
| #define KEY1 0x45670123 |
| #define KEY2 0xCDEF89AB |
| |
| static int unlock(void) |
| { |
| /* |
| * We may have already locked the flash module and get a bus fault |
| * in the attempt to unlock. Need to disable bus fault handler now. |
| */ |
| ignore_bus_fault(1); |
| |
| /* unlock CR if needed */ |
| if (STM32_FLASH_CR & FLASH_CR_LOCK) { |
| STM32_FLASH_KEYR = KEY1; |
| STM32_FLASH_KEYR = KEY2; |
| } |
| |
| /* Re-enable bus fault handler */ |
| ignore_bus_fault(0); |
| |
| return (STM32_FLASH_CR & FLASH_CR_LOCK) ? |
| EC_ERROR_UNKNOWN : EC_SUCCESS; |
| } |
| |
| static void lock(void) |
| { |
| STM32_FLASH_CR = FLASH_CR_LOCK; |
| } |
| |
| |
| int flash_physical_get_protect(int block) |
| { |
| /* TODO: not sure if write protect can be implemented like this. */ |
| return 0; |
| } |
| |
| uint32_t flash_physical_get_protect_flags(void) |
| { |
| return entire_flash_locked ? EC_FLASH_PROTECT_ALL_NOW : 0; |
| } |
| |
| int flash_physical_protect_now(int all) |
| { |
| if (all) { |
| /* |
| * Lock by writing a wrong key to FLASH_KEYR. This triggers a |
| * bus fault, so we need to disable bus fault handler while |
| * doing this. |
| * |
| * This incorrect key fault causes the flash to become |
| * permanenlty locked until reset, a correct keyring write |
| * will not unlock it. In this way we can implement system |
| * write protect. |
| */ |
| ignore_bus_fault(1); |
| STM32_FLASH_KEYR = 0xffffffff; |
| ignore_bus_fault(0); |
| |
| entire_flash_locked = 1; |
| |
| /* Check if lock happened */ |
| if (STM32_FLASH_CR & FLASH_CR_LOCK) |
| return EC_SUCCESS; |
| } |
| |
| /* No way to protect just the RO flash until next boot */ |
| return EC_ERROR_INVAL; |
| } |
| |
| uint32_t flash_physical_get_valid_flags(void) |
| { |
| return EC_FLASH_PROTECT_RO_AT_BOOT | |
| EC_FLASH_PROTECT_RO_NOW | |
| EC_FLASH_PROTECT_ALL_NOW; |
| } |
| |
| uint32_t flash_physical_get_writable_flags(uint32_t cur_flags) |
| { |
| uint32_t ret = 0; |
| |
| /* If RO protection isn't enabled, its at-boot state can be changed. */ |
| if (!(cur_flags & EC_FLASH_PROTECT_RO_NOW)) |
| ret |= EC_FLASH_PROTECT_RO_AT_BOOT; |
| |
| /* |
| * If entire flash isn't protected at this boot, it can be enabled if |
| * the WP GPIO is asserted. |
| */ |
| if (!(cur_flags & EC_FLASH_PROTECT_ALL_NOW) && |
| (cur_flags & EC_FLASH_PROTECT_GPIO_ASSERTED)) |
| ret |= EC_FLASH_PROTECT_ALL_NOW; |
| |
| return ret; |
| } |
| |
| int flash_physical_restore_state(void) |
| { |
| uint32_t reset_flags = system_get_reset_flags(); |
| int version, size; |
| const struct flash_wp_state *prev; |
| |
| /* |
| * If we have already jumped between images, an earlier image could |
| * have applied write protection. Nothing additional needs to be done. |
| */ |
| if (reset_flags & RESET_FLAG_SYSJUMP) { |
| prev = (const struct flash_wp_state *)system_get_jump_tag( |
| FLASH_SYSJUMP_TAG, &version, &size); |
| if (prev && version == FLASH_HOOK_VERSION && |
| size == sizeof(*prev)) |
| entire_flash_locked = prev->entire_flash_locked; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int flash_idle(void) |
| { |
| timestamp_t deadline; |
| |
| deadline.val = get_time().val + FLASH_TIMEOUT_US; |
| /* Wait for flash op to complete. |
| * This function is used for both reads and writes, so |
| * we need a long timeout, but a relatively short poll interval. |
| */ |
| while ((STM32_FLASH_SR & FLASH_SR_BUSY) && |
| (get_time().val < deadline.val)) { |
| usleep(1); |
| } |
| |
| if (STM32_FLASH_SR & FLASH_SR_BUSY) |
| return EC_ERROR_TIMEOUT; |
| |
| return EC_SUCCESS; |
| } |
| |
| static void clear_flash_errors(void) |
| { |
| /* Clear previous error status */ |
| STM32_FLASH_SR = FLASH_SR_ERR_MASK; |
| } |
| |
| /*****************************************************************************/ |
| /* Physical layer APIs */ |
| |
| int flash_physical_protect_at_boot(enum flash_wp_range range) |
| { |
| return EC_SUCCESS; |
| } |
| |
| int flash_physical_write(int offset, int size, const char *data) |
| { |
| uint32_t *address = (uint32_t *)(CONFIG_MAPPED_STORAGE_BASE + offset); |
| int res = EC_SUCCESS; |
| |
| if (unlock() != EC_SUCCESS) { |
| res = EC_ERROR_UNKNOWN; |
| goto exit_wr; |
| } |
| |
| /* Wait for busy to clear */ |
| res = flash_idle(); |
| if (res) |
| goto exit_wr; |
| clear_flash_errors(); |
| |
| /* set PG bit */ |
| STM32_FLASH_CR &= ~FLASH_CR_PSIZE_MASK; |
| STM32_FLASH_CR |= FLASH_CR_PSIZE(FLASH_CR_PSIZE_32); |
| STM32_FLASH_CR |= FLASH_CR_PG; |
| |
| for (; size > 0; size -= sizeof(uint32_t)) { |
| /* |
| * Reload the watchdog timer to avoid watchdog reset when doing |
| * long writing with interrupt disabled. |
| */ |
| watchdog_reload(); |
| |
| res = flash_idle(); |
| if (res) |
| goto exit_wr; |
| |
| /* write the word */ |
| *address = data[0] + (data[1] << 8) + |
| (data[2] << 16) + (data[3] << 24); |
| |
| address++; |
| data += sizeof(uint32_t); |
| |
| res = flash_idle(); |
| if (res) |
| goto exit_wr; |
| |
| if (STM32_FLASH_SR & FLASH_SR_BUSY) { |
| res = EC_ERROR_TIMEOUT; |
| goto exit_wr; |
| } |
| |
| /* Check for error conditions - erase failed, voltage error, |
| * protection error. |
| */ |
| if (STM32_FLASH_SR & FLASH_SR_ERR_MASK) { |
| res = EC_ERROR_UNKNOWN; |
| goto exit_wr; |
| } |
| } |
| |
| exit_wr: |
| /* Disable PG bit */ |
| STM32_FLASH_CR &= ~FLASH_CR_PG; |
| |
| lock(); |
| |
| return res; |
| } |
| |
| |
| |
| /* "@Internal Flash /0x08000000/04*016Kg,01*064Kg,03*128Kg" */ |
| struct flash_sector { |
| int base; |
| int size; |
| }; |
| static const struct flash_sector sectors[] = { |
| {(0 * 1024), (16 * 1024)}, |
| {(16 * 1024), (16 * 1024)}, |
| {(32 * 1024), (16 * 1024)}, |
| {(48 * 1024), (16 * 1024)}, |
| {(64 * 1024), (64 * 1024)}, |
| {(128 * 1024), (128 * 1024)}, |
| {(256 * 1024), (128 * 1024)}, |
| {(384 * 1024), (128 * 1024)} |
| }; |
| static const int num_sectors = ARRAY_SIZE(sectors); |
| |
| int flash_physical_erase(int offset, int size) |
| { |
| int res = EC_SUCCESS; |
| int start_sector; |
| int end_sector; |
| |
| /* Check that offset/size align with sectors. */ |
| for (start_sector = 0; start_sector < num_sectors; start_sector++) |
| if (offset == sectors[start_sector].base) |
| break; |
| for (end_sector = start_sector; end_sector < num_sectors; end_sector++) |
| if ((offset + size) == |
| (sectors[end_sector].base + sectors[end_sector].size)) |
| break; |
| |
| /* We can only erase on sector boundaries. */ |
| if ((start_sector >= num_sectors) || (end_sector >= num_sectors)) |
| return EC_ERROR_PARAM1; |
| |
| if (unlock() != EC_SUCCESS) |
| return EC_ERROR_UNKNOWN; |
| |
| res = flash_idle(); |
| if (res) |
| goto exit_er; |
| |
| clear_flash_errors(); |
| |
| for (; start_sector <= end_sector; start_sector++) { |
| /* Do nothing if already erased */ |
| if (flash_is_erased(sectors[start_sector].base, |
| sectors[start_sector].size)) |
| continue; |
| |
| res = flash_idle(); |
| if (res) |
| goto exit_er; |
| |
| /* set Sector Erase bit and select sector */ |
| STM32_FLASH_CR = (STM32_FLASH_CR & ~FLASH_CR_SNB_MASK) | |
| FLASH_CR_SER | FLASH_CR_SNB(start_sector); |
| |
| /* set STRT bit : start erase */ |
| STM32_FLASH_CR |= FLASH_CR_STRT; |
| |
| /* |
| * Reload the watchdog timer to avoid watchdog reset during a |
| * long erase operation. |
| */ |
| watchdog_reload(); |
| |
| /* Wait for erase to complete, this will be awhile */ |
| res = flash_idle(); |
| if (res) |
| goto exit_er; |
| /* |
| * Check for error conditions - erase failed, voltage error, |
| * protection error |
| */ |
| if (STM32_FLASH_SR & FLASH_SR_ERR_MASK) { |
| res = EC_ERROR_UNKNOWN; |
| goto exit_er; |
| } |
| } |
| |
| exit_er: |
| /* reset PER bit */ |
| STM32_FLASH_CR &= ~FLASH_CR_SER; |
| |
| lock(); |
| |
| return res; |
| } |
| |
| /*****************************************************************************/ |
| /* High-level APIs */ |
| |
| int flash_pre_init(void) |
| { |
| return EC_SUCCESS; |
| } |
| |
| /*****************************************************************************/ |
| /* Hooks */ |
| |
| static void flash_preserve_state(void) |
| { |
| struct flash_wp_state state; |
| |
| state.entire_flash_locked = entire_flash_locked; |
| |
| system_add_jump_tag(FLASH_SYSJUMP_TAG, FLASH_HOOK_VERSION, |
| sizeof(state), &state); |
| } |
| DECLARE_HOOK(HOOK_SYSJUMP, flash_preserve_state, HOOK_PRIO_DEFAULT); |
| |