summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-05-17 13:12:01 -0700
committerChromeBot <chrome-bot@google.com>2013-05-22 19:15:56 -0700
commite8ecda5e8d0384ddc8fe6b3bd9e991ee6d84faae (patch)
tree402184e035d6e792ccfa41fbdf4caf9b9c8abece
parent6592036c6c6b24744ac3bb1450ee41f0c7630b10 (diff)
downloadchrome-ec-e8ecda5e8d0384ddc8fe6b3bd9e991ee6d84faae.tar.gz
Support flash write protect on STM32L
This adds support for write protecting the RO code at boot, and the entire flash on demand. Implementation if WP# is not asserted is currently a little different than STM32F and LM4; RO is still protected at boot if ro_at_boot, but can be unprotected and the change will commit on the next reboot. This saves the bank of flash which we use for pstate on LM4 and STM32F. I think I can use one of the unused option bits (WRP2 bit 0) to hold the RO-at-boot flag, in which case I can more closely match the behavior of the other chips, but I'd like to do that (or give up and implement pstate) in a separate CL so it's clearer what I'm doing. BUG=chrome-os-partner:15613 BRANCH=none TEST=manual - flashinfo -> (no flags) - enable WP (via screw or dut-control) - flashinfo -> wp_gpio_asserted - flashwp enable - flashinfo -> wp_gpio_asserted ro_at_boot - flashwp now - flashinfo -> wp_gpio_asserted ro_at_boot all_now - flashwp disable -> fails - flashinfo -> wp_gpio_asserted ro_at_boot all_now - flasherase 0x1fc00 0x400 -> fails - reboot - flashinfo -> wp_gpio_asserted ro_at_boot ro_now - flasherase 0xfc00 0x400 -> fails - flasherase 0x1fc00 0x400 -> succeeds - disable WP (via screw or dut-control) - reboot - flashinfo -> ro_at_boot ro_now - flashwp disable - flashinfo -> ro_now - reboot - flashinfo -> (no flags) - flasherase 0xfc00 0x400 -> succeeds - flasherase 0x1fc00 0x400 -> succeeds Change-Id: Id1b6b099a44a1985a5ab9387feb882a8f26e3aa1 Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/55594
-rw-r--r--chip/stm32/flash-stm32l15x.c341
-rw-r--r--chip/stm32/registers.h14
-rw-r--r--chip/stm32/system.c12
-rw-r--r--common/flash_common.c9
-rw-r--r--include/flash.h11
5 files changed, 286 insertions, 101 deletions
diff --git a/chip/stm32/flash-stm32l15x.c b/chip/stm32/flash-stm32l15x.c
index eb97c16da7..d110267fa6 100644
--- a/chip/stm32/flash-stm32l15x.c
+++ b/chip/stm32/flash-stm32l15x.c
@@ -9,6 +9,7 @@
#include "flash.h"
#include "gpio.h"
#include "registers.h"
+#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
@@ -25,19 +26,6 @@
#define FLASH_TIMEOUT_LOOP \
(FLASH_TIMEOUT_US * (CPU_CLOCK / SECOND) / CYCLE_PER_FLASH_LOOP)
-/* Flash unlocking keys */
-#define PEKEY1 0x89ABCDEF
-#define PEKEY2 0x02030405
-#define PRGKEY1 0x8C9DAEBF
-#define PRGKEY2 0x13141516
-#define OPTKEY1 0xFBEAD9C8
-#define OPTKEY2 0x24252627
-
-/* Lock bits*/
-#define PE_LOCK (1<<0)
-#define PRG_LOCK (1<<1)
-#define OPT_LOCK (1<<2)
-
#define PHYSICAL_BANKS (CONFIG_FLASH_PHYSICAL_SIZE / CONFIG_FLASH_BANK_SIZE)
/* Read-only firmware offset and size in units of flash banks */
@@ -57,63 +45,117 @@ static uint32_t write_buffer[CONFIG_FLASH_WRITE_SIZE / sizeof(uint32_t)];
static int buffered_off = -1;
#endif
-/* TODO: (crosbug.com/p/15613) Verify write protect on stm32l */
-#undef STM32L_WP_VERIFIED
+/**
+ * Lock all the locks.
+ *
+ * @param until_next_boot If non-zero, prevent unlocking until next boot.
+ */
+static void lock(int until_next_boot)
+{
+ ignore_bus_fault(1);
+
+ /* Re-enable the locks */
+ STM32_FLASH_PECR = STM32_FLASH_PECR_PE_LOCK |
+ STM32_FLASH_PECR_PRG_LOCK | STM32_FLASH_PECR_OPT_LOCK;
+ /* If we need to lock until next boot, write a bad value to PEKEYR */
+ if (until_next_boot)
+ STM32_FLASH_PEKEYR = 0;
+
+ ignore_bus_fault(0);
+}
+
+/**
+ * Unlock the specified locks.
+ */
static int unlock(int locks)
{
- /* unlock PECR if needed */
- if (STM32_FLASH_PECR & PE_LOCK) {
- STM32_FLASH_PEKEYR = PEKEY1;
- STM32_FLASH_PEKEYR = PEKEY2;
+ /*
+ * 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 PECR if needed */
+ if (STM32_FLASH_PECR & STM32_FLASH_PECR_PE_LOCK) {
+ STM32_FLASH_PEKEYR = STM32_FLASH_PEKEYR_KEY1;
+ STM32_FLASH_PEKEYR = STM32_FLASH_PEKEYR_KEY2;
+ }
+
+ /* Fail if it didn't unlock */
+ if (STM32_FLASH_PECR & STM32_FLASH_PECR_PE_LOCK) {
+ ignore_bus_fault(0);
+ return EC_ERROR_ACCESS_DENIED;
}
- /* unlock program memory if required */
- if ((locks & PRG_LOCK) && (STM32_FLASH_PECR & PRG_LOCK)) {
- STM32_FLASH_PRGKEYR = PRGKEY1;
- STM32_FLASH_PRGKEYR = PRGKEY2;
+
+ /* Unlock program memory if required */
+ if ((locks & STM32_FLASH_PECR_PRG_LOCK) &&
+ (STM32_FLASH_PECR & STM32_FLASH_PECR_PRG_LOCK)) {
+ STM32_FLASH_PRGKEYR = STM32_FLASH_PRGKEYR_KEY1;
+ STM32_FLASH_PRGKEYR = STM32_FLASH_PRGKEYR_KEY2;
}
- /* unlock option memory if required */
- if ((locks & OPT_LOCK) && (STM32_FLASH_PECR & 4)) {
- STM32_FLASH_OPTKEYR = OPTKEY1;
- STM32_FLASH_OPTKEYR = OPTKEY2;
+
+ /* Unlock option memory if required */
+ if ((locks & STM32_FLASH_PECR_OPT_LOCK) &&
+ (STM32_FLASH_PECR & STM32_FLASH_PECR_OPT_LOCK)) {
+ STM32_FLASH_OPTKEYR = STM32_FLASH_OPTKEYR_KEY1;
+ STM32_FLASH_OPTKEYR = STM32_FLASH_OPTKEYR_KEY2;
}
- return (STM32_FLASH_PECR & (locks | PE_LOCK)) ?
- EC_ERROR_UNKNOWN : EC_SUCCESS;
+ ignore_bus_fault(0);
+
+ /* Successful if we unlocked everything we wanted */
+ if (!(STM32_FLASH_PECR & (locks | STM32_FLASH_PECR_PE_LOCK)))
+ return EC_SUCCESS;
+
+ /* Otherwise relock everything and return error */
+ lock(0);
+ return EC_ERROR_ACCESS_DENIED;
}
-static void lock(void)
+/**
+ * Read an option byte word.
+ *
+ * Option bytes are stored in pairs in 32-bit registers; the upper 16 bits is
+ * the 1's compliment of the lower 16 bits.
+ */
+static uint16_t read_optb(int offset)
{
- STM32_FLASH_PECR = 0x7;
+ return REG16(STM32_OPTB_BASE + offset);
}
-static uint8_t read_optb(int byte)
+/**
+ * Write an option byte word.
+ *
+ * Requires OPT_LOCK unlocked.
+ */
+static void write_optb(int offset, uint16_t value)
{
- return *(uint8_t *)(STM32_OPTB_BASE + byte);
-
+ REG32(STM32_OPTB_BASE + offset) =
+ (uint32_t)value | ((uint32_t)(~value) << 16);
}
-#ifdef STM32L_WP_VERIFIED
-static void write_optb(int byte, uint8_t value)
+/**
+ * Read the at-boot protection option bits.
+ */
+static uint32_t read_optb_wrp(void)
{
- volatile int32_t *word = (uint32_t *)(STM32_OPTB_BASE + (byte & ~0x3));
- uint32_t val = *word;
- int shift = (byte & 0x3) * 8;
-
- if (unlock(OPT_LOCK) != EC_SUCCESS)
- return;
-
- val &= ~((0xff << shift) | (0xff << (shift + STM32_OPTB_COMPL_SHIFT)));
- val |= (value << shift) | (~value << (shift + STM32_OPTB_COMPL_SHIFT));
- *word = val;
-
- /* TODO reboot by writing OBL_LAUNCH bit ? */
+ return read_optb(STM32_OPTB_WRP01) |
+ ((uint32_t)read_optb(STM32_OPTB_WRP23) << 16);
+}
- lock();
+/**
+ * Write the at-boot protection option bits.
+ */
+static void write_optb_wrp(uint32_t value)
+{
+ write_optb(STM32_OPTB_WRP01, (uint16_t)value);
+ write_optb(STM32_OPTB_WRP23, value >> 16);
}
-#endif
/**
+ * Write data to flash.
+ *
* This function lives in internal RAM, as we cannot read flash during writing.
* You must not call other functions from this one or declare it static.
*/
@@ -124,15 +166,15 @@ void __attribute__((section(".iram.text")))
interrupt_disable();
- /* wait to be ready */
+ /* Wait for ready */
for (i = 0; (STM32_FLASH_SR & 1) && (i < FLASH_TIMEOUT_LOOP) ;
i++)
;
- /* set PROG and FPRG bits */
- STM32_FLASH_PECR |= (1<<3) | (1<<10);
+ /* Set PROG and FPRG bits */
+ STM32_FLASH_PECR |= STM32_FLASH_PECR_PROG | STM32_FLASH_PECR_FPRG;
- /* send words for the half page */
+ /* Send words for the half page */
for (i = 0; i < CONFIG_FLASH_WRITE_SIZE / sizeof(uint32_t); i++)
*addr++ = *data++;
@@ -142,7 +184,7 @@ void __attribute__((section(".iram.text")))
;
/* Disable PROG and FPRG bits */
- STM32_FLASH_PECR &= ~((1<<3) | (1<<10));
+ STM32_FLASH_PECR &= ~(STM32_FLASH_PECR_PROG | STM32_FLASH_PECR_FPRG);
interrupt_enable();
}
@@ -158,32 +200,31 @@ int flash_physical_write(int offset, int size, const char *data)
int res = EC_SUCCESS;
#ifdef CONFIG_64B_WORKAROUND
- if ((size < CONFIG_FLASH_WRITE_SIZE) || (offset & 64)) {
- if ((size != 64) ||
- ((offset & 64) && (buffered_off != offset - 64))) {
- res = EC_ERROR_UNKNOWN;
- goto exit_wr;
-
- }
- if (offset & 64) {
- /* second 64B packet : flash ! */
- memcpy(write_buffer + 16, data32, 64);
- offset -= 64;
- size += 64;
- data32 = write_buffer;
- } else {
- /* first 64B packet : just store it */
- buffered_off = offset;
- memcpy(write_buffer, data32, 64);
- return EC_SUCCESS;
- }
+ if ((size < CONFIG_FLASH_WRITE_SIZE) || (offset & 64)) {
+ if ((size != 64) ||
+ ((offset & 64) && (buffered_off != offset - 64))) {
+ res = EC_ERROR_UNKNOWN;
+ goto exit_wr;
+ }
+ if (offset & 64) {
+ /* second 64B packet : flash ! */
+ memcpy(write_buffer + 16, data32, 64);
+ offset -= 64;
+ size += 64;
+ data32 = write_buffer;
+ } else {
+ /* first 64B packet : just store it */
+ buffered_off = offset;
+ memcpy(write_buffer, data32, 64);
+ return EC_SUCCESS;
}
+ }
#endif
- if (unlock(PRG_LOCK) != EC_SUCCESS) {
- res = EC_ERROR_UNKNOWN;
+ /* Unlock program area */
+ res = unlock(STM32_FLASH_PECR_PRG_LOCK);
+ if (res)
goto exit_wr;
- }
/* Clear previous error status */
STM32_FLASH_SR = 0xf00;
@@ -217,7 +258,8 @@ int flash_physical_write(int offset, int size, const char *data)
}
exit_wr:
- lock();
+ /* Relock program lock */
+ lock(0);
return res;
}
@@ -227,14 +269,15 @@ int flash_physical_erase(int offset, int size)
uint32_t *address;
int res = EC_SUCCESS;
- if (unlock(PRG_LOCK) != EC_SUCCESS)
- return EC_ERROR_UNKNOWN;
+ res = unlock(STM32_FLASH_PECR_PRG_LOCK);
+ if (res)
+ return res;
/* Clear previous error status */
STM32_FLASH_SR = 0xf00;
- /* set PROG and ERASE bits */
- STM32_FLASH_PECR |= (1<<3) | (1<<9);
+ /* Set PROG and ERASE bits */
+ STM32_FLASH_PECR |= STM32_FLASH_PECR_PROG | STM32_FLASH_PECR_ERASE;
for (address = (uint32_t *)(CONFIG_FLASH_BASE + offset) ;
size > 0; size -= CONFIG_FLASH_ERASE_SIZE,
@@ -280,33 +323,79 @@ int flash_physical_erase(int offset, int size)
}
exit_er:
- lock();
+ /* Disable program and erase, and relock PECR */
+ STM32_FLASH_PECR &= ~(STM32_FLASH_PECR_PROG | STM32_FLASH_PECR_ERASE);
+ lock(0);
return res;
}
int flash_physical_get_protect(int block)
{
- uint8_t val = read_optb(STM32_OPTB_WRP_OFF(block/8));
- return val & (1 << (block % 8));
+ /* Check the active write protect status */
+ return STM32_FLASH_WRPR & (1 << block);
}
-void flash_physical_set_protect(int start_bank, int bank_count)
+static int flash_physical_set_protect(int start_bank, int bank_count,
+ int enable)
{
-#ifdef STM32L_WP_VERIFIED
- int block;
+ uint32_t prot;
+ uint32_t mask = ((1 << bank_count) - 1) << start_bank;
+ int rv;
- for (block = start_bank; block < start_bank + bank_count; block++) {
- int byte_off = STM32_OPTB_WRP_OFF(block/8);
- uint8_t val = read_optb(byte_off) | (1 << (block % 8));
- write_optb(byte_off, val);
- }
-#endif
+ /* Read the current protection status */
+ prot = read_optb_wrp();
+
+ /* Set/clear bits */
+ if (enable)
+ prot |= mask;
+ else
+ prot &= ~mask;
+
+ if (prot == read_optb_wrp())
+ return EC_SUCCESS; /* No bits changed */
+
+ /* Unlock option bytes */
+ rv = unlock(STM32_FLASH_PECR_OPT_LOCK);
+ if (rv)
+ return rv;
+
+ /* Update them */
+ write_optb_wrp(prot);
+
+ /* Relock */
+ lock(0);
+
+ /*
+ * Note that on STM32L, the flash protection bits are only read from
+ * the option bytes at power-on or if OBL_LAUNCH is set in PECR (which
+ * causes a reboot). Until then, the previous protection bits apply.
+ * We take care of the reboot in flash_pre_init().
+ */
+
+ return EC_SUCCESS;
+}
+
+int flash_physical_force_reload(void)
+{
+ int rv = unlock(STM32_FLASH_PECR_OPT_LOCK);
+
+ if (rv)
+ return rv;
+
+ /* Force a reboot; this should never return. */
+ STM32_FLASH_PECR = STM32_FLASH_PECR_OBL_LAUNCH;
+ while (1)
+ ;
+
+ return EC_ERROR_UNKNOWN;
}
uint32_t flash_get_protect(void)
{
uint32_t flags = 0;
+ uint32_t prot;
+ uint32_t prot_ro_mask = ((1 << RO_BANK_COUNT) - 1) << RO_BANK_OFFSET;
int not_protected[2] = {0};
int i;
@@ -314,6 +403,18 @@ uint32_t flash_get_protect(void)
if (gpio_get_level(GPIO_WP_L) == 0)
flags |= EC_FLASH_PROTECT_GPIO_ASSERTED;
+ /* Check RO at-boot protection */
+ prot = read_optb_wrp() & prot_ro_mask;
+ if (prot) {
+ /* At least one RO bank will be protected at boot */
+ flags |= EC_FLASH_PROTECT_RO_AT_BOOT;
+
+ if (prot != prot_ro_mask) {
+ /* But not all RO banks! */
+ flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT;
+ }
+ }
+
/* Scan flash protection */
for (i = 0; i < PHYSICAL_BANKS; i++) {
/* Is this bank part of RO? */
@@ -335,16 +436,64 @@ uint32_t flash_get_protect(void)
}
}
+ /* If we can't unlock, all flash is protected now */
+ if (unlock(STM32_FLASH_PECR_PE_LOCK))
+ flags |= EC_FLASH_PROTECT_ALL_NOW;
+ lock(0);
+
return flags;
}
int flash_set_protect(uint32_t mask, uint32_t flags)
{
- /* TODO: (crosbug.com/p/15613) implement! */
- return EC_SUCCESS;
+ int retval = EC_SUCCESS;
+ int rv;
+
+ /*
+ * Note that we process flags we can set. Track the most recent error,
+ * but process all flags before returning.
+ *
+ * Start with the persistent state of at-boot protection.
+ */
+ if (mask & EC_FLASH_PROTECT_RO_AT_BOOT) {
+ rv = flash_physical_set_protect(RO_BANK_OFFSET, RO_BANK_COUNT,
+ flags & EC_FLASH_PROTECT_RO_AT_BOOT);
+ if (rv)
+ retval = rv;
+ }
+
+ /*
+ * 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;
+
+ /*
+ * No way to protect just RO now if it wasn't protected at boot, so
+ * ignore setting EC_FLASH_PROTECT_RO_NOW.
+ *
+ * ALL_NOW works, though.
+ */
+ if ((mask & EC_FLASH_PROTECT_ALL_NOW) &&
+ (flags & EC_FLASH_PROTECT_ALL_NOW)) {
+ /* Protect the entire flash */
+ lock(1);
+ }
+
+ return retval;
}
int flash_pre_init(void)
{
+ /*
+ * Check if the active protection matches the desired protection. If
+ * it doesn't, force a hard reboot so that the chip re-reads the
+ * protection bits from the option bytes.
+ */
+ if (STM32_FLASH_WRPR != read_optb_wrp())
+ system_reset(SYSTEM_RESET_HARD | SYSTEM_RESET_PRESERVE_FLAGS);
+
return EC_SUCCESS;
}
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index 2edc9d2fb2..148d1f2e08 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -475,10 +475,23 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t;
#define STM32_FLASH_ACR REG32(STM32_FLASH_REGS_BASE + 0x00)
#define STM32_FLASH_PECR REG32(STM32_FLASH_REGS_BASE + 0x04)
+#define STM32_FLASH_PECR_PE_LOCK (1 << 0)
+#define STM32_FLASH_PECR_PRG_LOCK (1 << 1)
+#define STM32_FLASH_PECR_OPT_LOCK (1 << 2)
+#define STM32_FLASH_PECR_PROG (1 << 3)
+#define STM32_FLASH_PECR_ERASE (1 << 9)
+#define STM32_FLASH_PECR_FPRG (1 << 10)
+#define STM32_FLASH_PECR_OBL_LAUNCH (1 << 18)
#define STM32_FLASH_PDKEYR REG32(STM32_FLASH_REGS_BASE + 0x08)
#define STM32_FLASH_PEKEYR REG32(STM32_FLASH_REGS_BASE + 0x0c)
+#define STM32_FLASH_PEKEYR_KEY1 0x89ABCDEF
+#define STM32_FLASH_PEKEYR_KEY2 0x02030405
#define STM32_FLASH_PRGKEYR REG32(STM32_FLASH_REGS_BASE + 0x10)
+#define STM32_FLASH_PRGKEYR_KEY1 0x8C9DAEBF
+#define STM32_FLASH_PRGKEYR_KEY2 0x13141516
#define STM32_FLASH_OPTKEYR REG32(STM32_FLASH_REGS_BASE + 0x14)
+#define STM32_FLASH_OPTKEYR_KEY1 0xFBEAD9C8
+#define STM32_FLASH_OPTKEYR_KEY2 0x24252627
#define STM32_FLASH_SR REG32(STM32_FLASH_REGS_BASE + 0x18)
#define STM32_FLASH_OBR REG32(STM32_FLASH_REGS_BASE + 0x1c)
#define STM32_FLASH_WRPR REG32(STM32_FLASH_REGS_BASE + 0x20)
@@ -487,6 +500,7 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t;
#define STM32_OPTB_RDP_OFF 0x00
#define STM32_OPTB_USER_OFF 0x04
+#define STM32_OPTB_WRP01 0x08
#define STM32_OPTB_WRP_OFF(n) (0x08 + (n&1) + (n&2) * 2)
#define STM32_OPTB_WRP23 0x0c
diff --git a/chip/stm32/system.c b/chip/stm32/system.c
index ae46dbf800..18e9edf044 100644
--- a/chip/stm32/system.c
+++ b/chip/stm32/system.c
@@ -7,6 +7,7 @@
#include "console.h"
#include "cpu.h"
+#include "flash.h"
#include "registers.h"
#include "system.h"
#include "task.h"
@@ -178,6 +179,17 @@ void system_reset(int flags)
bkpdata_write(BKPDATA_INDEX_SAVED_RESET_FLAGS, save_flags | console_en);
if (flags & SYSTEM_RESET_HARD) {
+
+#ifdef CHIP_VARIANT_stm32l15x
+ /*
+ * Ask the flash module to reboot, so that we reload the
+ * option bytes.
+ */
+ flash_physical_force_reload();
+
+ /* Fall through to watchdog if that fails */
+#endif
+
/* Ask the watchdog to trigger a hard reboot */
STM32_IWDG_KR = 0x5555;
STM32_IWDG_RLR = 0x1;
diff --git a/common/flash_common.c b/common/flash_common.c
index ff421ad2a7..c7767c031f 100644
--- a/common/flash_common.c
+++ b/common/flash_common.c
@@ -352,7 +352,10 @@ static int flash_command_protect(struct host_cmd_handler_args *args)
if (!(r->flags & EC_FLASH_PROTECT_RO_NOW))
r->writable_flags |= EC_FLASH_PROTECT_RO_AT_BOOT;
-#ifdef CHIP_lm4
+#if defined(CHIP_VARIANT_stm32f100) || defined(CHIP_VARIANT_stm32f10x)
+ r->valid_flags |= EC_FLASH_PROTECT_ALL_NOW;
+ r->writable_flags |= EC_FLASH_PROTECT_ALL_NOW;
+#else
/*
* If entire flash isn't protected at this boot, it can be enabled if
* the WP GPIO is asserted.
@@ -360,10 +363,6 @@ static int flash_command_protect(struct host_cmd_handler_args *args)
if (!(r->flags & EC_FLASH_PROTECT_ALL_NOW) &&
(r->flags & EC_FLASH_PROTECT_GPIO_ASSERTED))
r->writable_flags |= EC_FLASH_PROTECT_ALL_NOW;
-
-#elif defined(CHIP_stm32)
- r->valid_flags |= EC_FLASH_PROTECT_ALL_NOW;
- r->writable_flags |= EC_FLASH_PROTECT_ALL_NOW;
#endif
args->response_size = sizeof(*r);
diff --git a/include/flash.h b/include/flash.h
index 9a9454bf52..fe592e3478 100644
--- a/include/flash.h
+++ b/include/flash.h
@@ -70,6 +70,17 @@ int flash_physical_erase(int offset, int size);
*/
int flash_physical_get_protect(int bank);
+/**
+ * Force reload of flash protection bits.
+ *
+ * Some EC architectures (STM32L) only load the bits from option bytes at
+ * power-on reset or via a special command. This issues that command if
+ * possible, which triggers a power-on reboot.
+ *
+ * Only returns (with EC_ERROR_ACCESS_DENIED) if the command is locked.
+ */
+int flash_physical_force_reload(void);
+
/*****************************************************************************/
/* High-level interface for use by other modules. */