diff options
-rw-r--r-- | chip/stm32/config-stm32f100.h | 10 | ||||
-rw-r--r-- | chip/stm32/flash-stm32f100.c | 295 | ||||
-rw-r--r-- | chip/stm32/system.c | 31 | ||||
-rw-r--r-- | common/flash_common.c | 8 |
4 files changed, 311 insertions, 33 deletions
diff --git a/chip/stm32/config-stm32f100.h b/chip/stm32/config-stm32f100.h index bb4c4c33dc..dfb59c3cbe 100644 --- a/chip/stm32/config-stm32f100.h +++ b/chip/stm32/config-stm32f100.h @@ -17,7 +17,8 @@ #define CONFIG_FW_IMAGE_SIZE (64 * 1024) #define CONFIG_FW_RO_OFF 0 -#define CONFIG_FW_RO_SIZE CONFIG_FW_IMAGE_SIZE +#define CONFIG_FW_RO_SIZE (CONFIG_FW_IMAGE_SIZE \ + - CONFIG_SECTION_FLASH_PSTATE_SIZE) #define CONFIG_FW_RW_OFF CONFIG_FW_IMAGE_SIZE #define CONFIG_FW_RW_SIZE CONFIG_FW_IMAGE_SIZE @@ -27,12 +28,11 @@ #define CONFIG_SECTION_RW_SIZE CONFIG_FW_RW_SIZE /* - * The EC uses the top bank of flash to emulate a SPI-like write protect - * register with persistent state. Put that up at the top. + * Put this after RO to give RW more space. This also makes RO write protect + * region contiguous. */ #define CONFIG_SECTION_FLASH_PSTATE_SIZE (1 * CONFIG_FLASH_BANK_SIZE) -#define CONFIG_SECTION_FLASH_PSTATE_OFF (CONFIG_FLASH_SIZE \ - - CONFIG_SECTION_FLASH_PSTATE_SIZE) +#define CONFIG_SECTION_FLASH_PSTATE_OFF CONFIG_FW_RO_OFF + CONFIG_FW_RO_SIZE /* Number of IRQ vectors on the NVIC */ #define CONFIG_IRQ_COUNT 61 diff --git a/chip/stm32/flash-stm32f100.c b/chip/stm32/flash-stm32f100.c index 3f9f732609..2b2ee4ca46 100644 --- a/chip/stm32/flash-stm32f100.c +++ b/chip/stm32/flash-stm32f100.c @@ -9,6 +9,7 @@ #include "flash.h" #include "registers.h" #include "power_button.h" +#include "system.h" #include "task.h" #include "timer.h" #include "util.h" @@ -42,13 +43,38 @@ #define PHYSICAL_BANKS (CONFIG_FLASH_PHYSICAL_SIZE / CONFIG_FLASH_BANK_SIZE) +/* Persistent protection state flash offset / size / bank */ +#define PSTATE_OFFSET CONFIG_SECTION_FLASH_PSTATE_OFF +#define PSTATE_SIZE CONFIG_SECTION_FLASH_PSTATE_SIZE +#define PSTATE_BANK (PSTATE_OFFSET / CONFIG_FLASH_BANK_SIZE) +#define PSTATE_BANK_COUNT (PSTATE_SIZE / CONFIG_FLASH_BANK_SIZE) + /* Read-only firmware offset and size in units of flash banks */ #define RO_BANK_OFFSET (CONFIG_SECTION_RO_OFF / CONFIG_FLASH_BANK_SIZE) #define RO_BANK_COUNT (CONFIG_SECTION_RO_SIZE / CONFIG_FLASH_BANK_SIZE) -/* Fake write protect switch for flash write protect development. - * TODO: Remove this when we have real write protect pin. */ -static int fake_write_protect; +/* Read-write firmware offset and size in units of flash banks */ +#define RW_BANK_OFFSET (CONFIG_SECTION_RW_OFF / CONFIG_FLASH_BANK_SIZE) +#define RW_BANK_COUNT (CONFIG_SECTION_RW_SIZE / CONFIG_FLASH_BANK_SIZE) + +/* Persistent protection state - emulates a SPI status register for flashrom */ +struct persist_state { + uint8_t version; /* Version of this struct */ + uint8_t flags; /* Lock flags (PERSIST_FLAG_*) */ + uint8_t reserved[2]; /* Reserved; set 0 */ +}; + +#define PERSIST_STATE_VERSION 2 /* Expected persist_state.version */ + +/* Flags for persist_state.flags */ +/* Protect persist state and RO firmware at boot */ +#define PERSIST_FLAG_PROTECT_RO 0x02 + +/* Functions defined in system.c to access backup registers */ +int system_set_flash_rw_at_boot(int val); +int system_get_flash_rw_at_boot(void); +int system_set_fake_wp(int val); +int system_get_fake_wp(void); static void write_optb(int byte, uint8_t value); @@ -155,6 +181,10 @@ static void write_optb(int byte, uint8_t value) /* Try to erase that byte back to 0xff. */ preserve_optb(byte); + /* The value is 0xff after erase. No need to write 0xff again. */ + if (value == 0xff) + return; + if (unlock(OPT_LOCK) != EC_SUCCESS) return; @@ -170,6 +200,54 @@ static void write_optb(int byte, uint8_t value) lock(); } +/** + * Read persistent state into pstate. + */ +static int read_pstate(struct persist_state *pstate) +{ + memcpy(pstate, flash_physical_dataptr(PSTATE_OFFSET), sizeof(*pstate)); + + /* Sanity-check data and initialize if necessary */ + 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. + */ +static int write_pstate(const struct persist_state *pstate) +{ + struct persist_state current_pstate; + int rv; + + /* Check if pstate has actually changed */ + if (!read_pstate(¤t_pstate) && + !memcmp(¤t_pstate, pstate, sizeof(*pstate))) + return EC_SUCCESS; + + /* Erase pstate */ + rv = flash_physical_erase(PSTATE_OFFSET, 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. + */ + + /* Rewrite the data */ + return flash_physical_write(PSTATE_OFFSET, sizeof(*pstate), + (const char *)pstate); +} + +/*****************************************************************************/ +/* Physical layer APIs */ + int flash_physical_write(int offset, int size, const char *data) { uint16_t *address = (uint16_t *)(CONFIG_FLASH_BASE + offset); @@ -291,30 +369,114 @@ exit_er: return res; } - int flash_physical_get_protect(int block) { + return !(STM32_FLASH_WRPR & (1 << block)); +} + +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))); + return (!(val & (1 << (block % 8)))) ? 1 : 0; } -void flash_physical_set_protect(int start_bank, int bank_count) +static void flash_physical_set_protect_at_boot(int start_bank, + int bank_count, + int enable) { int block; int i; - int original_val[8], val[8]; + int original_val[4], val[4]; - for (i = 0; i < 8; ++i) - original_val[i] = val[i] = read_optb(i * 2); + for (i = 0; i < 4; ++i) + original_val[i] = val[i] = read_optb(i * 2 + 8); for (block = start_bank; block < start_bank + bank_count; block++) { - int byte_off = STM32_OPTB_WRP_OFF(block/8) / 2; - val[byte_off] = val[byte_off] & (~(1 << (block % 8))); + int byte_off = STM32_OPTB_WRP_OFF(block/8) / 2 - 4; + if (enable) + val[byte_off] = val[byte_off] & (~(1 << (block % 8))); + else + val[byte_off] = val[byte_off] | (1 << (block % 8)); } - for (i = 0; i < 8; ++i) + for (i = 0; i < 4; ++i) if (original_val[i] != val[i]) - write_optb(i * 2, val[i]); + write_optb(i * 2 + 8, val[i]); +} + +static int protect_ro_at_boot(int enable, int force) +{ + struct persist_state pstate; + int new_flags = enable ? PERSIST_FLAG_PROTECT_RO : 0; + int rv; + + /* Read the current persist state from flash */ + rv = read_pstate(&pstate); + if (rv) + return rv; + + /* Update state if necessary */ + if (pstate.flags != new_flags || force) { + /* Fail if write protect block is already locked */ + if (flash_physical_get_protect(PSTATE_BANK)) + return EC_ERROR_ACCESS_DENIED; + + /* Set the new flag */ + pstate.flags = new_flags; + + /* Write the state back to flash */ + rv = write_pstate(&pstate); + if (rv) + return rv; + + /* + * Write to write protect register. + * Since we already wrote to pstate, ignore error here. + */ + flash_physical_set_protect_at_boot(RO_BANK_OFFSET, + RO_BANK_COUNT + PSTATE_BANK_COUNT, new_flags); + } + + return EC_SUCCESS; +} + +static int protect_rw_at_boot(int enable, int force) +{ + int old_flag = system_get_flash_rw_at_boot() ? 1 : 0; + int new_flag = enable ? 1 : 0; + + /* Update state if necessary */ + if (old_flag != new_flag || force) { + system_set_flash_rw_at_boot(new_flag); + flash_physical_set_protect_at_boot(RW_BANK_OFFSET, + RW_BANK_COUNT, + new_flag); + } + + return EC_SUCCESS; +} + +/** + * Determine if write protect register is inconsistent with RO_AT_BOOT and + * RW_AT_BOOT state. + */ +static int register_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 rw_at_boot = (flags & EC_FLASH_PROTECT_RW_AT_BOOT) ? 1 : 0; + int ro_wp_region_start = RO_BANK_OFFSET; + int ro_wp_region_end = + RO_BANK_OFFSET + RO_BANK_COUNT + PSTATE_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; + for (i = RW_BANK_OFFSET; i < RW_BANK_OFFSET + RW_BANK_COUNT; i++) + if (flash_physical_get_protect_at_boot(i) != rw_at_boot) + return 1; + return 0; } static void unprotect_all_blocks(void) @@ -324,42 +486,102 @@ static void unprotect_all_blocks(void) write_optb(i * 2, 0xff); } +/*****************************************************************************/ +/* High-level APIs */ + int flash_pre_init(void) { - /* Drop write protect status here. If a block should be protected, - * write protect for it will be set by pstate. */ - unprotect_all_blocks(); + uint32_t reset_flags = system_get_reset_flags(); + uint32_t prot_flags = flash_get_protect(); + int need_reset = 0; /* - * TODO: enable/disable write protect based on pstate (RO) and - * RTC register (RW). + * 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 say "ro_at_boot". WP register says otherwise. + * Listen to pstate. + */ + protect_ro_at_boot(1, 1); + need_reset = 1; + } + + if (register_need_reset()) { + /* + * Reset RO protect register to make sure this doesn't + * happen again due to RO protect state inconsistency. + */ + protect_ro_at_boot( + prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT, 1); + /* And reset RW protect register */ + protect_rw_at_boot( + prot_flags & EC_FLASH_PROTECT_RW_AT_BOOT, 1); + need_reset = 1; + } + } + else { + if ((prot_flags & EC_FLASH_PROTECT_RO_NOW) || + (prot_flags & EC_FLASH_PROTECT_RW_NOW)) { + /* + * Write protect pin unasserted but some section is + * protected. Drop it and reboot. + */ + unprotect_all_blocks(); + need_reset = 1; + } + } + + if (need_reset) + system_reset(SYSTEM_RESET_HARD | SYSTEM_RESET_PRESERVE_FLAGS); + return EC_SUCCESS; } uint32_t flash_get_protect(void) { + struct persist_state pstate; uint32_t flags = 0; int i; + int not_protected[2] = {0}; /* TODO (vpalatin) : write protect scheme for stm32 */ - if (fake_write_protect) + if (system_get_fake_wp()) flags |= EC_FLASH_PROTECT_GPIO_ASSERTED; + /* Read the current persist state from flash */ + read_pstate(&pstate); + if (pstate.flags & PERSIST_FLAG_PROTECT_RO) + flags |= EC_FLASH_PROTECT_RO_AT_BOOT; + + /* Read the current persist state from flash */ + if (system_get_flash_rw_at_boot()) + flags |= EC_FLASH_PROTECT_RW_AT_BOOT; + /* Scan flash protection */ for (i = 0; i < PHYSICAL_BANKS; i++) { /* Is this bank part of RO? */ int is_ro = (i >= RO_BANK_OFFSET && - i < RO_BANK_OFFSET + RO_BANK_COUNT); + i < RO_BANK_OFFSET + RO_BANK_COUNT + + PSTATE_BANK_COUNT) ? 1 : 0; int bank_flag = (is_ro ? EC_FLASH_PROTECT_RO_NOW : EC_FLASH_PROTECT_RW_NOW); if (flash_physical_get_protect(i)) { - /* At least one bank in the region is protected */ flags |= bank_flag; - } else if (flags & bank_flag) { - /* But not all banks in the region! */ - flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT; + if (not_protected[is_ro]) + flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT; + } + else { + not_protected[is_ro] = 1; + if (flags & bank_flag) + flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT; } } @@ -368,8 +590,27 @@ uint32_t flash_get_protect(void) int flash_set_protect(uint32_t mask, uint32_t flags) { - /* TODO: implement! */ - return EC_SUCCESS; + int retval = EC_SUCCESS; + int rv; + + /* + * Process flags we can set. Track the most recent error, but process + * all flags before returning. + */ + + if (mask & EC_FLASH_PROTECT_RO_AT_BOOT) { + rv = protect_ro_at_boot(flags & EC_FLASH_PROTECT_RO_AT_BOOT, 0); + if (rv) + retval = rv; + } + + if (mask & EC_FLASH_PROTECT_RW_AT_BOOT) { + rv = protect_rw_at_boot(flags & EC_FLASH_PROTECT_RW_AT_BOOT, 0); + if (rv) + retval = rv; + } + + return retval; } static int command_set_fake_wp(int argc, char **argv) @@ -384,7 +625,7 @@ static int command_set_fake_wp(int argc, char **argv) if (*e) return EC_ERROR_PARAM1; - fake_write_protect = val; + system_set_fake_wp(val); ccprintf("Fake write protect = %d\n", val); return EC_SUCCESS; diff --git a/chip/stm32/system.c b/chip/stm32/system.c index 026bcc8bb7..f8b00371f0 100644 --- a/chip/stm32/system.c +++ b/chip/stm32/system.c @@ -16,6 +16,11 @@ enum bkpdata_index { BKPDATA_INDEX_SCRATCHPAD, /* General-purpose scratchpad */ BKPDATA_INDEX_WAKE, /* Wake reasons for hibernate */ BKPDATA_INDEX_SAVED_RESET_FLAGS,/* Saved reset flags */ + BKPDATA_INDEX_FLASH_RW_AT_BOOT, /* Flash protect RW at boot flag */ + BKPDATA_INDEX_FAKE_WP, /* Fake write-protect pin */ + /* TODO: Remove this when we have real + * write protect pin. + */ }; /* Wake reason flags for hibernate */ @@ -269,12 +274,38 @@ const char *system_get_chip_vendor(void) return "stm"; } + const char *system_get_chip_name(void) { return STRINGIFY(CHIP_VARIANT); } + const char *system_get_chip_revision(void) { return ""; } + + +int system_set_fake_wp(int val) +{ + return bkpdata_write(BKPDATA_INDEX_FAKE_WP, (uint16_t)val); +} + + +int system_get_fake_wp(void) +{ + return bkpdata_read(BKPDATA_INDEX_FAKE_WP); +} + + +int system_set_flash_rw_at_boot(int val) +{ + return bkpdata_write(BKPDATA_INDEX_FLASH_RW_AT_BOOT, (uint16_t)val); +} + + +int system_get_flash_rw_at_boot(void) +{ + return bkpdata_read(BKPDATA_INDEX_FLASH_RW_AT_BOOT); +} diff --git a/common/flash_common.c b/common/flash_common.c index b2cb5fbaf5..84a2a2d55a 100644 --- a/common/flash_common.c +++ b/common/flash_common.c @@ -93,6 +93,8 @@ static int command_flash_info(int argc, char **argv) ccputs(" wp_gpio_asserted"); if (i & EC_FLASH_PROTECT_RO_AT_BOOT) ccputs(" ro_at_boot"); + if (i & EC_FLASH_PROTECT_RW_AT_BOOT) + ccputs(" rw_at_boot"); if (i & EC_FLASH_PROTECT_RO_NOW) ccputs(" ro_now"); if (i & EC_FLASH_PROTECT_RW_NOW) @@ -192,11 +194,15 @@ static int command_flash_wp(int argc, char **argv) else if (!strcasecmp(argv[1], "now")) return flash_set_protect(EC_FLASH_PROTECT_RW_NOW | EC_FLASH_PROTECT_RO_NOW, -1); + else if (!strcasecmp(argv[1], "rw")) + return flash_set_protect(EC_FLASH_PROTECT_RW_AT_BOOT, -1); + else if (!strcasecmp(argv[1], "norw")) + return flash_set_protect(EC_FLASH_PROTECT_RW_AT_BOOT, 0); else return EC_ERROR_PARAM1; } DECLARE_CONSOLE_COMMAND(flashwp, command_flash_wp, - "<enable | disable | now>", + "<enable | disable | now | rw | norw>", "Modify flash write protect", NULL); |