| /* Copyright 2014 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. |
| */ |
| |
| /* Common flash memory module for STM32F and STM32F0 */ |
| |
| #include "battery.h" |
| #include "console.h" |
| #include "clock.h" |
| #include "flash.h" |
| #include "hooks.h" |
| #include "registers.h" |
| #include "panic.h" |
| #include "system.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "watchdog.h" |
| |
| /* |
| * Approximate number of CPU cycles per iteration of the loop when polling |
| * the flash status |
| */ |
| #define CYCLE_PER_FLASH_LOOP 10 |
| |
| /* |
| * While flash write / erase is in progress, the stm32 CPU core is mostly |
| * non-functional, due to the inability to fetch instructions from flash. |
| * This may greatly increase interrupt latency. |
| */ |
| |
| /* Flash page programming timeout. This is 2x the datasheet max. */ |
| #define FLASH_WRITE_TIMEOUT_US 16000 |
| /* 20ms < tERASE < 40ms on F0/F3, for 1K / 2K sector size. */ |
| #define FLASH_ERASE_TIMEOUT_US 40000 |
| |
| static inline int calculate_flash_timeout(void) |
| { |
| return (FLASH_WRITE_TIMEOUT_US * |
| (clock_get_freq() / SECOND) / CYCLE_PER_FLASH_LOOP); |
| } |
| |
| static int wait_busy(void) |
| { |
| int timeout = calculate_flash_timeout(); |
| while ((STM32_FLASH_SR & FLASH_SR_BUSY) && timeout-- > 0) |
| udelay(CYCLE_PER_FLASH_LOOP); |
| return (timeout > 0) ? EC_SUCCESS : EC_ERROR_TIMEOUT; |
| } |
| |
| |
| /* |
| * We at least unlock the control register lock. |
| * We may also unlock other locks. |
| */ |
| enum extra_lock_type { |
| NO_EXTRA_LOCK = 0, |
| OPT_LOCK = 1, |
| }; |
| |
| static int unlock(int locks) |
| { |
| /* |
| * 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); |
| |
| /* Always unlock CR if needed */ |
| if (STM32_FLASH_CR & FLASH_CR_LOCK) { |
| STM32_FLASH_KEYR = FLASH_KEYR_KEY1; |
| STM32_FLASH_KEYR = FLASH_KEYR_KEY2; |
| } |
| /* unlock option memory if required */ |
| if ((locks & OPT_LOCK) && STM32_FLASH_OPT_LOCKED) { |
| STM32_FLASH_OPTKEYR = FLASH_OPTKEYR_KEY1; |
| STM32_FLASH_OPTKEYR = FLASH_OPTKEYR_KEY2; |
| } |
| |
| /* Re-enable bus fault handler */ |
| ignore_bus_fault(0); |
| |
| if ((locks & OPT_LOCK) && STM32_FLASH_OPT_LOCKED) |
| return EC_ERROR_UNKNOWN; |
| if (STM32_FLASH_CR & FLASH_CR_LOCK) |
| return EC_ERROR_UNKNOWN; |
| return EC_SUCCESS; |
| } |
| |
| static void lock(void) |
| { |
| #if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3) |
| /* FLASH_CR_OPTWRE was set by writing the keys in unlock(). */ |
| STM32_FLASH_CR &= ~FLASH_CR_OPTWRE; |
| #endif |
| STM32_FLASH_CR |= FLASH_CR_LOCK; |
| } |
| |
| #ifdef CHIP_FAMILY_STM32F4 |
| static int write_optb(uint32_t mask, uint32_t value) |
| { |
| int rv; |
| |
| rv = wait_busy(); |
| if (rv) |
| return rv; |
| |
| /* The target byte is the value we want to write. */ |
| if ((STM32_FLASH_OPTCR & mask) == value) |
| return EC_SUCCESS; |
| |
| rv = unlock(OPT_LOCK); |
| if (rv) |
| return rv; |
| |
| STM32_FLASH_OPTCR = (STM32_FLASH_OPTCR & ~mask) | value; |
| STM32_FLASH_OPTCR |= FLASH_OPTSTRT; |
| |
| rv = wait_busy(); |
| if (rv) |
| return rv; |
| lock(); |
| |
| return EC_SUCCESS; |
| } |
| #else |
| static int write_optb(int byte, uint8_t value); |
| /* |
| * Option byte organization |
| * |
| * [31:24] [23:16] [15:8] [7:0] |
| * |
| * 0x1FFF_F800 nUSER USER nRDP RDP |
| * |
| * 0x1FFF_F804 nData1 Data1 nData0 Data0 |
| * |
| * 0x1FFF_F808 nWRP1 WRP1 nWRP0 WRP0 |
| * |
| * 0x1FFF_F80C nWRP3 WRP2 nWRP2 WRP2 |
| * |
| * Note that the variable with n prefix means the complement. |
| */ |
| static uint8_t read_optb(int byte) |
| { |
| return *(uint8_t *)(STM32_OPTB_BASE + byte); |
| } |
| |
| static int erase_optb(void) |
| { |
| int rv; |
| |
| rv = wait_busy(); |
| if (rv) |
| return rv; |
| |
| rv = unlock(OPT_LOCK); |
| if (rv) |
| return rv; |
| |
| /* Must be set in 2 separate lines. */ |
| STM32_FLASH_CR |= FLASH_CR_OPTER; |
| STM32_FLASH_CR |= FLASH_CR_STRT; |
| |
| rv = wait_busy(); |
| |
| STM32_FLASH_CR &= ~FLASH_CR_OPTER; |
| |
| if (rv) |
| return rv; |
| lock(); |
| |
| return EC_SUCCESS; |
| } |
| |
| static int write_optb(int byte, uint8_t value); |
| /* |
| * Since the option byte erase is WHOLE erase, this function is to keep |
| * rest of bytes, but make this byte 0xff. |
| * Note that this could make a recursive call to write_optb(). |
| */ |
| static int preserve_optb(int byte) |
| { |
| int i, rv; |
| uint8_t optb[8]; |
| |
| /* The byte has been reset, no need to run preserve. */ |
| if (*(uint16_t *)(STM32_OPTB_BASE + byte) == 0xffff) |
| return EC_SUCCESS; |
| |
| for (i = 0; i < ARRAY_SIZE(optb); ++i) |
| optb[i] = read_optb(i * 2); |
| |
| optb[byte / 2] = 0xff; |
| |
| rv = erase_optb(); |
| if (rv) |
| return rv; |
| for (i = 0; i < ARRAY_SIZE(optb); ++i) { |
| rv = write_optb(i * 2, optb[i]); |
| if (rv) |
| return rv; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| static int write_optb(int byte, uint8_t value) |
| { |
| volatile int16_t *hword = (uint16_t *)(STM32_OPTB_BASE + byte); |
| int rv; |
| |
| rv = wait_busy(); |
| if (rv) |
| return rv; |
| |
| /* The target byte is the value we want to write. */ |
| if (*(uint8_t *)hword == value) |
| return EC_SUCCESS; |
| |
| /* Try to erase that byte back to 0xff. */ |
| rv = preserve_optb(byte); |
| if (rv) |
| return rv; |
| |
| /* The value is 0xff after erase. No need to write 0xff again. */ |
| if (value == 0xff) |
| return EC_SUCCESS; |
| |
| rv = unlock(OPT_LOCK); |
| if (rv) |
| return rv; |
| |
| /* set OPTPG bit */ |
| STM32_FLASH_CR |= FLASH_CR_OPTPG; |
| |
| *hword = ((~value) << STM32_OPTB_COMPL_SHIFT) | value; |
| |
| /* reset OPTPG bit */ |
| STM32_FLASH_CR &= ~FLASH_CR_OPTPG; |
| |
| rv = wait_busy(); |
| if (rv) |
| return rv; |
| lock(); |
| |
| return EC_SUCCESS; |
| } |
| #endif |
| |
| /*****************************************************************************/ |
| /* Physical layer APIs */ |
| |
| int flash_physical_write(int offset, int size, const char *data) |
| { |
| #if CONFIG_FLASH_WRITE_SIZE == 1 |
| uint8_t *address = (uint8_t *)(CONFIG_PROGRAM_MEMORY_BASE + offset); |
| uint8_t quantum = 0; |
| #elif CONFIG_FLASH_WRITE_SIZE == 2 |
| uint16_t *address = (uint16_t *)(CONFIG_PROGRAM_MEMORY_BASE + offset); |
| uint16_t quantum = 0; |
| #elif CONFIG_FLASH_WRITE_SIZE == 4 |
| uint32_t *address = (uint32_t *)(CONFIG_PROGRAM_MEMORY_BASE + offset); |
| uint32_t quantum = 0; |
| #else |
| #error "CONFIG_FLASH_WRITE_SIZE not supported." |
| #endif |
| int res = EC_SUCCESS; |
| int timeout = calculate_flash_timeout(); |
| |
| if (unlock(NO_EXTRA_LOCK) != EC_SUCCESS) { |
| res = EC_ERROR_UNKNOWN; |
| goto exit_wr; |
| } |
| |
| /* Clear previous error status */ |
| STM32_FLASH_SR = FLASH_SR_ALL_ERR | FLASH_SR_EOP; |
| |
| /* set PG bit */ |
| STM32_FLASH_CR |= FLASH_CR_PG; |
| |
| for (; size > 0; size -= CONFIG_FLASH_WRITE_SIZE) { |
| int i; |
| |
| for (i = CONFIG_FLASH_WRITE_SIZE - 1, quantum = 0; i >= 0; i--) |
| quantum = (quantum << 8) + data[i]; |
| data += CONFIG_FLASH_WRITE_SIZE; |
| /* |
| * Reload the watchdog timer to avoid watchdog reset when doing |
| * long writing with interrupt disabled. |
| */ |
| watchdog_reload(); |
| |
| /* wait to be ready */ |
| for (i = 0; |
| (STM32_FLASH_SR & FLASH_SR_BUSY) && |
| (i < timeout); |
| i++) |
| ; |
| |
| /* write the data */ |
| *address++ = quantum; |
| |
| /* Wait for writes to complete */ |
| for (i = 0; |
| (STM32_FLASH_SR & FLASH_SR_BUSY) && |
| (i < timeout); |
| i++) |
| ; |
| |
| 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_ALL_ERR) { |
| res = EC_ERROR_UNKNOWN; |
| goto exit_wr; |
| } |
| } |
| |
| exit_wr: |
| /* Disable PG bit */ |
| STM32_FLASH_CR &= ~FLASH_CR_PG; |
| |
| lock(); |
| |
| return res; |
| } |
| |
| int flash_physical_erase(int offset, int size) |
| { |
| int res = EC_SUCCESS; |
| int sector_size; |
| int timeout_us; |
| #ifdef CHIP_FAMILY_STM32F4 |
| int sector = flash_bank_index(offset); |
| /* we take advantage of sector_size == erase_size */ |
| if ((sector < 0) || (flash_bank_index(offset + size) < 0)) |
| return EC_ERROR_INVAL; /* Invalid range */ |
| #endif |
| |
| if (unlock(NO_EXTRA_LOCK) != EC_SUCCESS) |
| return EC_ERROR_UNKNOWN; |
| |
| /* Clear previous error status */ |
| STM32_FLASH_SR = FLASH_SR_ALL_ERR | FLASH_SR_EOP; |
| |
| /* set SER/PER bit */ |
| STM32_FLASH_CR |= FLASH_CR_PER; |
| |
| while (size > 0) { |
| timestamp_t deadline; |
| #ifdef CHIP_FAMILY_STM32F4 |
| sector_size = flash_bank_size(sector); |
| /* Timeout: from spec, proportional to the size |
| * inversely proportional to the write size. |
| */ |
| timeout_us = sector_size * 4 / CONFIG_FLASH_WRITE_SIZE; |
| #else |
| sector_size = CONFIG_FLASH_ERASE_SIZE; |
| timeout_us = FLASH_ERASE_TIMEOUT_US; |
| #endif |
| /* Do nothing if already erased */ |
| if (flash_is_erased(offset, sector_size)) |
| goto next_sector; |
| #ifdef CHIP_FAMILY_STM32F4 |
| /* select page to erase */ |
| STM32_FLASH_CR = (STM32_FLASH_CR & ~STM32_FLASH_CR_SNB_MASK) | |
| (sector << STM32_FLASH_CR_SNB_OFFSET); |
| #else |
| /* select page to erase */ |
| STM32_FLASH_AR = CONFIG_PROGRAM_MEMORY_BASE + offset; |
| #endif |
| /* set STRT bit : start erase */ |
| STM32_FLASH_CR |= FLASH_CR_STRT; |
| |
| deadline.val = get_time().val + timeout_us; |
| /* Wait for erase to complete */ |
| watchdog_reload(); |
| while ((STM32_FLASH_SR & FLASH_SR_BUSY) && |
| (get_time().val < deadline.val)) { |
| usleep(timeout_us/100); |
| } |
| if (STM32_FLASH_SR & FLASH_SR_BUSY) { |
| res = EC_ERROR_TIMEOUT; |
| goto exit_er; |
| } |
| |
| /* |
| * Check for error conditions - erase failed, voltage error, |
| * protection error |
| */ |
| if (STM32_FLASH_SR & FLASH_SR_ALL_ERR) { |
| res = EC_ERROR_UNKNOWN; |
| goto exit_er; |
| } |
| next_sector: |
| size -= sector_size; |
| offset += sector_size; |
| #ifdef CHIP_FAMILY_STM32F4 |
| sector++; |
| #endif |
| } |
| |
| exit_er: |
| /* reset SER/PER bit */ |
| STM32_FLASH_CR &= ~FLASH_CR_PER; |
| |
| lock(); |
| |
| return res; |
| } |
| |
| #ifdef CHIP_FAMILY_STM32F4 |
| static int flash_physical_get_protect_at_boot(int block) |
| { |
| /* 0: Write protection active on sector i. */ |
| return !(STM32_OPTB_WP & STM32_OPTB_nWRP(block)); |
| } |
| |
| int flash_physical_protect_at_boot(uint32_t new_flags) |
| { |
| int block; |
| int original_val, val; |
| |
| original_val = val = STM32_OPTB_WP & STM32_OPTB_nWRP_ALL; |
| |
| for (block = WP_BANK_OFFSET; |
| block < WP_BANK_OFFSET + PHYSICAL_BANKS; |
| block++) { |
| int protect = new_flags & EC_FLASH_PROTECT_ALL_AT_BOOT; |
| |
| if (block >= WP_BANK_OFFSET && |
| block < WP_BANK_OFFSET + WP_BANK_COUNT) |
| protect |= new_flags & EC_FLASH_PROTECT_RO_AT_BOOT; |
| #ifdef CONFIG_FLASH_PROTECT_RW |
| else |
| protect |= new_flags & EC_FLASH_PROTECT_RW_AT_BOOT; |
| #endif |
| |
| if (protect) |
| val &= ~(1 << block); |
| else |
| val |= 1 << block; |
| } |
| if (original_val != val) { |
| write_optb(STM32_FLASH_nWRP_ALL, |
| val << STM32_FLASH_nWRP_OFFSET); |
| } |
| |
| |
| return EC_SUCCESS; |
| } |
| |
| static void unprotect_all_blocks(void) |
| { |
| write_optb(STM32_FLASH_nWRP_ALL, STM32_FLASH_nWRP_ALL); |
| } |
| |
| #else /* CHIP_FAMILY_STM32F4 */ |
| static int flash_physical_get_protect_at_boot(int block) |
| { |
| uint8_t val = read_optb(STM32_OPTB_WRP_OFF(block/8)); |
| return (!(val & (1 << (block % 8)))) ? 1 : 0; |
| } |
| |
| int flash_physical_protect_at_boot(uint32_t new_flags) |
| { |
| int block; |
| int i; |
| int original_val[4], val[4]; |
| |
| for (i = 0; i < 4; ++i) |
| original_val[i] = val[i] = read_optb(i * 2 + 8); |
| |
| for (block = WP_BANK_OFFSET; |
| block < WP_BANK_OFFSET + PHYSICAL_BANKS; |
| block++) { |
| int protect = new_flags & EC_FLASH_PROTECT_ALL_AT_BOOT; |
| int byte_off = STM32_OPTB_WRP_OFF(block/8) / 2 - 4; |
| |
| if (block >= WP_BANK_OFFSET && |
| block < WP_BANK_OFFSET + WP_BANK_COUNT) |
| protect |= new_flags & EC_FLASH_PROTECT_RO_AT_BOOT; |
| #ifdef CONFIG_ROLLBACK |
| else if (block >= ROLLBACK_BANK_OFFSET && |
| block < ROLLBACK_BANK_OFFSET + ROLLBACK_BANK_COUNT) |
| protect |= new_flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT; |
| #endif |
| #ifdef CONFIG_FLASH_PROTECT_RW |
| else |
| protect |= new_flags & EC_FLASH_PROTECT_RW_AT_BOOT; |
| #endif |
| |
| if (protect) |
| val[byte_off] = val[byte_off] & (~(1 << (block % 8))); |
| else |
| val[byte_off] = val[byte_off] | (1 << (block % 8)); |
| } |
| |
| for (i = 0; i < 4; ++i) |
| if (original_val[i] != val[i]) |
| write_optb(i * 2 + 8, val[i]); |
| |
| #ifdef CONFIG_WP_ALWAYS |
| /* |
| * Set a permanent protection by increasing RDP to level 1, |
| * trying to unprotected the flash will trigger a full erase. |
| */ |
| write_optb(0, 0x11); |
| #endif |
| |
| return EC_SUCCESS; |
| } |
| |
| static void unprotect_all_blocks(void) |
| { |
| int i; |
| |
| for (i = 4; i < 8; ++i) |
| write_optb(i * 2, 0xff); |
| } |
| #endif |
| |
| /** |
| * Check if write protect register state is inconsistent with RO_AT_BOOT and |
| * ALL_AT_BOOT state. |
| * |
| * @return zero if consistent, non-zero if inconsistent. |
| */ |
| static int registers_need_reset(void) |
| { |
| uint32_t flags = flash_get_protect(); |
| int i; |
| int ro_at_boot = (flags & EC_FLASH_PROTECT_RO_AT_BOOT) ? 1 : 0; |
| int ro_wp_region_start = WP_BANK_OFFSET; |
| int ro_wp_region_end = WP_BANK_OFFSET + WP_BANK_COUNT; |
| |
| for (i = ro_wp_region_start; i < ro_wp_region_end; i++) |
| if (flash_physical_get_protect_at_boot(i) != ro_at_boot) |
| return 1; |
| return 0; |
| } |
| |
| /*****************************************************************************/ |
| /* High-level APIs */ |
| |
| int flash_pre_init(void) |
| { |
| uint32_t reset_flags = system_get_reset_flags(); |
| uint32_t prot_flags = flash_get_protect(); |
| int need_reset = 0; |
| |
| |
| #ifdef CHIP_FAMILY_STM32F4 |
| unlock(NO_EXTRA_LOCK); |
| /* Set the proper write size */ |
| STM32_FLASH_CR = (STM32_FLASH_CR & ~STM32_FLASH_CR_PSIZE_MASK) | |
| (31 - __builtin_clz(CONFIG_FLASH_WRITE_SIZE)) << |
| STM32_FLASH_CR_PSIZE_OFFSET; |
| lock(); |
| #endif |
| if (flash_physical_restore_state()) |
| return EC_SUCCESS; |
| |
| /* |
| * 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) |
| return EC_SUCCESS; |
| |
| if (prot_flags & EC_FLASH_PROTECT_GPIO_ASSERTED) { |
| if ((prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT) && |
| !(prot_flags & EC_FLASH_PROTECT_RO_NOW)) { |
| /* |
| * Pstate wants RO protected at boot, but the write |
| * protect register wasn't set to protect it. Force an |
| * update to the write protect register and reboot so |
| * it takes effect. |
| */ |
| flash_physical_protect_at_boot( |
| EC_FLASH_PROTECT_RO_AT_BOOT); |
| need_reset = 1; |
| } |
| |
| if (registers_need_reset()) { |
| /* |
| * Write protect register was in an inconsistent state. |
| * Set it back to a good state and reboot. |
| * |
| * TODO(crosbug.com/p/23798): this seems really similar |
| * to the check above. One of them should be able to |
| * go away. |
| */ |
| flash_protect_at_boot( |
| prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT); |
| need_reset = 1; |
| } |
| } else { |
| if (prot_flags & EC_FLASH_PROTECT_RO_NOW) { |
| /* |
| * Write protect pin unasserted but some section is |
| * protected. Drop it and reboot. |
| */ |
| unprotect_all_blocks(); |
| need_reset = 1; |
| } |
| } |
| |
| if ((flash_physical_get_valid_flags() & EC_FLASH_PROTECT_ALL_AT_BOOT) && |
| (!!(prot_flags & EC_FLASH_PROTECT_ALL_AT_BOOT) != |
| !!(prot_flags & EC_FLASH_PROTECT_ALL_NOW))) { |
| /* |
| * ALL_AT_BOOT and ALL_NOW should be both set or both unset |
| * at boot. If they are not, it must be that the chip requires |
| * OBL_LAUNCH to be set to reload option bytes. Let's reset |
| * the system with OBL_LAUNCH set. |
| * This assumes OBL_LAUNCH is used for hard reset in |
| * chip/stm32/system.c. |
| */ |
| need_reset = 1; |
| } |
| |
| #ifdef CONFIG_FLASH_PROTECT_RW |
| if ((flash_physical_get_valid_flags() & EC_FLASH_PROTECT_RW_AT_BOOT) && |
| (!!(prot_flags & EC_FLASH_PROTECT_RW_AT_BOOT) != |
| !!(prot_flags & EC_FLASH_PROTECT_RW_NOW))) { |
| /* RW_AT_BOOT and RW_NOW do not match. */ |
| need_reset = 1; |
| } |
| #endif |
| |
| #ifdef CONFIG_ROLLBACK |
| if ((flash_physical_get_valid_flags() & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT) && |
| (!!(prot_flags & EC_FLASH_PROTECT_ROLLBACK_AT_BOOT) != |
| !!(prot_flags & EC_FLASH_PROTECT_ROLLBACK_NOW))) { |
| /* ROLLBACK_AT_BOOT and ROLLBACK_NOW do not match. */ |
| need_reset = 1; |
| } |
| #endif |
| |
| if (need_reset) |
| system_reset(SYSTEM_RESET_HARD | SYSTEM_RESET_PRESERVE_FLAGS); |
| |
| return EC_SUCCESS; |
| } |