diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 0d474daa43891..0470df9bf739b 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -47,6 +47,7 @@ if(CONFIG_SOC_FLASH_STM32) zephyr_library_sources_ifdef(CONFIG_DT_HAS_ST_STM32H7_FLASH_CONTROLLER_ENABLED flash_stm32h7x.c) else() zephyr_library_sources(flash_stm32.c) + zephyr_library_sources_ifdef(CONFIG_FLASH_EX_OP_ENABLED flash_stm32_ex_op.c) zephyr_library_sources_ifdef(CONFIG_DT_HAS_ST_STM32F1_FLASH_CONTROLLER_ENABLED flash_stm32f1x.c) zephyr_library_sources_ifdef(CONFIG_DT_HAS_ST_STM32F2_FLASH_CONTROLLER_ENABLED flash_stm32f2x.c) diff --git a/drivers/flash/Kconfig.stm32 b/drivers/flash/Kconfig.stm32 index 6aaa56a4ca264..55aa1c7dcca44 100644 --- a/drivers/flash/Kconfig.stm32 +++ b/drivers/flash/Kconfig.stm32 @@ -3,6 +3,7 @@ # Copyright (c) 2016 RnDity Sp. z o.o. # Copyright (c) 2017 BayLibre, SAS # Copyright (c) 2022 Linaro Limited +# Copyright (c) 2023 Google Inc # SPDX-License-Identifier: Apache-2.0 config SOC_FLASH_STM32 @@ -13,6 +14,60 @@ config SOC_FLASH_STM32 default y select FLASH_PAGE_LAYOUT select FLASH_HAS_PAGE_LAYOUT + select FLASH_HAS_EX_OP if SOC_SERIES_STM32F4X select MPU_ALLOW_FLASH_WRITE if ARM_MPU help Enable flash driver for STM32 series + +if SOC_FLASH_STM32 + +config FLASH_STM32_WRITE_PROTECT + bool "Extended operation for flash write protection control" + depends on SOC_SERIES_STM32F4X + default n + help + Enables flash extended operation for enabling/disabling flash write + protection. + +config FLASH_STM32_WRITE_PROTECT_DISABLE_PREVENTION + bool "Prevent from disabling flash write protection" + depends on FLASH_STM32_WRITE_PROTECT + default n + help + If enabled, all requests to disable flash write protection will be + blocked. + +config FLASH_STM32_READOUT_PROTECTION + bool "Extended operation for flash readout protection control" + depends on SOC_SERIES_STM32F4X + default n + help + Enables flash extended operation for enabling/disabling flash readout + protection. + +config FLASH_STM32_READOUT_PROTECTION_DISABLE_ALLOW + bool "Allow disabling readout protection" + depends on FLASH_STM32_READOUT_PROTECTION + default n + help + With this option enabled it will be possible to disable readout + protection. On STM32 devices it will trigger flash mass erase! + +config FLASH_STM32_READOUT_PROTECTION_PERMANENT_ALLOW + bool "Allow enabling readout protection permanently" + depends on FLASH_STM32_READOUT_PROTECTION + default n + help + With this option enabled it will be possible to enable readout + protection permanently. + +config FLASH_STM32_BLOCK_REGISTERS + bool "Extended operation for blocking option and control registers" + default n + help + Enables flash extended operations that can be used to disable access + to option and control registers until reset. Disabling access to these + registers improves system security, because flash content (or + protection settings) can't be changed even when exploit was found. + +endif # SOC_FLASH_STM32 diff --git a/drivers/flash/flash_stm32.c b/drivers/flash/flash_stm32.c index 28ba84692e602..1e9c2762a2012 100644 --- a/drivers/flash/flash_stm32.c +++ b/drivers/flash/flash_stm32.c @@ -2,6 +2,7 @@ * Copyright (c) 2017 Linaro Limited * Copyright (c) 2017 BayLibre, SAS. * Copyright (c) 2019 Centaur Analytics, Inc + * Copyright (c) 2023 Google Inc * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +14,7 @@ #include #include +#include #include #include #include @@ -306,6 +308,135 @@ static int flash_stm32_write_protection(const struct device *dev, bool enable) return rc; } +int flash_stm32_option_bytes_lock(const struct device *dev, bool enable) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + +#if defined(FLASH_OPTCR_OPTLOCK) /* F2, F4, F7 and H7 */ + if (enable) { + regs->OPTCR |= FLASH_OPTCR_OPTLOCK; + } else if (regs->OPTCR & FLASH_OPTCR_OPTLOCK) { + regs->OPTKEYR = FLASH_OPT_KEY1; + regs->OPTKEYR = FLASH_OPT_KEY2; + } +#else + int rc; + + /* Unlock CR/PECR/NSCR register if needed. */ + if (!enable) { + rc = flash_stm32_write_protection(dev, false); + if (rc) { + return rc; + } + } +#if defined(FLASH_CR_OPTWRE) /* F0, F1 and F3 */ + if (enable) { + regs->CR &= ~FLASH_CR_OPTWRE; + } else if (!(regs->CR & FLASH_CR_OPTWRE)) { + regs->OPTKEYR = FLASH_OPTKEY1; + regs->OPTKEYR = FLASH_OPTKEY2; + } +#elif defined(FLASH_CR_OPTLOCK) /* G0, G4, L4, WB and WL */ + if (enable) { + regs->CR |= FLASH_CR_OPTLOCK; + } else if (regs->CR & FLASH_CR_OPTLOCK) { + regs->OPTKEYR = FLASH_OPTKEY1; + regs->OPTKEYR = FLASH_OPTKEY2; + } +#elif defined(FLASH_PECR_OPTLOCK) /* L0 and L1 */ + if (enable) { + regs->PECR |= FLASH_PECR_OPTLOCK; + } else if (regs->PECR & FLASH_PECR_OPTLOCK) { + regs->OPTKEYR = FLASH_OPTKEY1; + regs->OPTKEYR = FLASH_OPTKEY2; + } +#elif defined(FLASH_NSCR_OPTLOCK) /* L5 and U5 */ + if (enable) { + regs->NSCR |= FLASH_NSCR_OPTLOCK; + } else if (regs->NSCR & FLASH_NSCR_OPTLOCK) { + regs->OPTKEYR = FLASH_OPTKEY1; + regs->OPTKEYR = FLASH_OPTKEY2; + } +#endif + /* Lock CR/PECR/NSCR register if needed. */ + if (enable) { + rc = flash_stm32_write_protection(dev, true); + if (rc) { + return rc; + } + } +#endif + + if (enable) { + LOG_DBG("Option bytes locked"); + } else { + LOG_DBG("Option bytes unlocked"); + } + + return 0; +} + +#if defined(CONFIG_FLASH_STM32_BLOCK_REGISTERS) +static int flash_stm32_control_register_disable(const struct device *dev) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + +#if defined(FLASH_CR_LOCK) /* F0, F1, F2, F3, F4, F7, L4, G0, G4, H7, WB, WL \ + */ + /* + * Access to control register can be disabled by writing wrong key to + * the key register. Option register will remain disabled until reset. + * Writing wrong key causes a bus fault, so we need to set FAULTMASK to + * disable faults, and clear bus fault pending bit before enabling them + * again. + */ + regs->CR |= FLASH_CR_LOCK; + + __set_FAULTMASK(1); + regs->KEYR = 0xffffffff; + + /* Clear Bus Fault pending bit */ + SCB->SHCSR &= ~SCB_SHCSR_BUSFAULTPENDED_Msk; + __set_FAULTMASK(0); + + return 0; +#else + ARG_UNUSED(regs); + + return -ENOTSUP; +#endif +} + +static int flash_stm32_option_bytes_disable(const struct device *dev) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + +#if defined(FLASH_OPTCR_OPTLOCK) /* F2, F4, F7 and H7 */ + /* + * Access to option register can be disabled by writing wrong key to + * the key register. Option register will remain disabled until reset. + * Writing wrong key causes a bus fault, so we need to set FAULTMASK to + * disable faults, and clear bus fault pending bit before enabling them + * again. + */ + regs->OPTCR |= FLASH_OPTCR_OPTLOCK; + + __set_FAULTMASK(1); + regs->OPTKEYR = 0xffffffff; + + /* Clear Bus Fault pending bit */ + SCB->SHCSR &= ~SCB_SHCSR_BUSFAULTPENDED_Msk; + __set_FAULTMASK(0); + + return 0; +#else + ARG_UNUSED(regs); + + return -ENOTSUP; +#endif +} +#endif /* CONFIG_FLASH_STM32_BLOCK_REGISTERS */ + static const struct flash_parameters * flash_stm32_get_parameters(const struct device *dev) { @@ -314,6 +445,41 @@ flash_stm32_get_parameters(const struct device *dev) return &flash_stm32_parameters; } +#ifdef CONFIG_FLASH_EX_OP_ENABLED +static int flash_stm32_ex_op(const struct device *dev, uint16_t code, + const uintptr_t in, void *out) +{ + int rv = -ENOTSUP; + + flash_stm32_sem_take(dev); + + switch (code) { +#if defined(CONFIG_FLASH_STM32_WRITE_PROTECT) + case FLASH_STM32_EX_OP_SECTOR_WP: + rv = flash_stm32_ex_op_sector_wp(dev, in, out); + break; +#endif /* CONFIG_FLASH_STM32_WRITE_PROTECT */ +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION) + case FLASH_STM32_EX_OP_RDP: + rv = flash_stm32_ex_op_rdp(dev, in, out); + break; +#endif /* CONFIG_FLASH_STM32_READOUT_PROTECTION */ +#if defined(CONFIG_FLASH_STM32_BLOCK_REGISTERS) + case FLASH_STM32_EX_OP_BLOCK_OPTION_REG: + rv = flash_stm32_option_bytes_disable(dev); + break; + case FLASH_STM32_EX_OP_BLOCK_CONTROL_REG: + rv = flash_stm32_control_register_disable(dev); + break; +#endif /* CONFIG_FLASH_STM32_BLOCK_REGISTERS */ + } + + flash_stm32_sem_give(dev); + + return rv; +} +#endif + static struct flash_stm32_priv flash_data = { .regs = (FLASH_TypeDef *) DT_INST_REG_ADDR(0), /* Getting clocks information from device tree description depending @@ -335,6 +501,9 @@ static const struct flash_driver_api flash_stm32_api = { #ifdef CONFIG_FLASH_PAGE_LAYOUT .page_layout = flash_stm32_page_layout, #endif +#ifdef CONFIG_FLASH_EX_OP_ENABLED + .ex_op = flash_stm32_ex_op, +#endif }; static int stm32_flash_init(const struct device *dev) diff --git a/drivers/flash/flash_stm32.h b/drivers/flash/flash_stm32.h index a503d80d22d4e..e6cf7011d8a79 100644 --- a/drivers/flash/flash_stm32.h +++ b/drivers/flash/flash_stm32.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2017 Linaro Limited * Copyright (c) 2017 BayLibre, SAS. + * Copyright (c) 2023 Google Inc * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +9,8 @@ #ifndef ZEPHYR_DRIVERS_FLASH_FLASH_STM32_H_ #define ZEPHYR_DRIVERS_FLASH_FLASH_STM32_H_ +#include + #if DT_NODE_HAS_PROP(DT_INST(0, st_stm32_flash_controller), clocks) || \ DT_NODE_HAS_PROP(DT_INST(0, st_stm32h7_flash_controller), clocks) #include @@ -195,6 +198,14 @@ struct flash_stm32_priv { FLASH_STM32_SR_RDERR | \ FLASH_STM32_SR_PGPERR) +#define FLASH_STM32_RDP0 0xAA +#define FLASH_STM32_RDP2 0xCC +#define FLASH_STM32_RDP1 \ + DT_PROP(DT_INST(0, st_stm32_flash_controller), st_rdp1_enable_byte) + +#if FLASH_STM32_RDP1 == FLASH_STM32_RDP0 || FLASH_STM32_RDP1 == FLASH_STM32_RDP2 +#error RDP1 byte has to be different than RDP0 and RDP2 byte +#endif #ifdef CONFIG_FLASH_PAGE_LAYOUT static inline bool flash_stm32_range_exists(const struct device *dev, @@ -220,6 +231,8 @@ int flash_stm32_block_erase_loop(const struct device *dev, int flash_stm32_wait_flash_idle(const struct device *dev); +int flash_stm32_option_bytes_lock(const struct device *dev, bool enable); + #ifdef CONFIG_SOC_SERIES_STM32WBX int flash_stm32_check_status(const struct device *dev); #endif /* CONFIG_SOC_SERIES_STM32WBX */ @@ -230,4 +243,32 @@ void flash_stm32_page_layout(const struct device *dev, size_t *layout_size); #endif +#if defined(CONFIG_FLASH_STM32_WRITE_PROTECT) + +int flash_stm32_update_wp_sectors(const struct device *dev, + uint32_t changed_sectors, + uint32_t protected_sectors); + +int flash_stm32_get_wp_sectors(const struct device *dev, + uint32_t *protected_sectors); +#endif +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION) + +int flash_stm32_update_rdp(const struct device *dev, bool enable, + bool permanent); + +int flash_stm32_get_rdp(const struct device *dev, bool *enabled, + bool *permanent); +#endif + +/* Flash extended operations */ +#if defined(CONFIG_FLASH_STM32_WRITE_PROTECT) +int flash_stm32_ex_op_sector_wp(const struct device *dev, const uintptr_t in, + void *out); +#endif +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION) +int flash_stm32_ex_op_rdp(const struct device *dev, const uintptr_t in, + void *out); +#endif + #endif /* ZEPHYR_DRIVERS_FLASH_FLASH_STM32_H_ */ diff --git a/drivers/flash/flash_stm32_ex_op.c b/drivers/flash/flash_stm32_ex_op.c new file mode 100644 index 0000000000000..1e2d801ffa59d --- /dev/null +++ b/drivers/flash/flash_stm32_ex_op.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 Google Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#ifdef CONFIG_USERSPACE +#include +#include +#endif + +#include +#include "flash_stm32.h" + +#if defined(CONFIG_FLASH_STM32_WRITE_PROTECT) +int flash_stm32_ex_op_sector_wp(const struct device *dev, const uintptr_t in, + void *out) +{ + const struct flash_stm32_ex_op_sector_wp_in *request = + (const struct flash_stm32_ex_op_sector_wp_in *)in; + struct flash_stm32_ex_op_sector_wp_out *result = + (struct flash_stm32_ex_op_sector_wp_out *)out; + uint32_t change_mask; + int rc = 0, rc2 = 0; +#ifdef CONFIG_USERSPACE + bool syscall_trap = z_syscall_trap(); +#endif + + if (request != NULL) { +#ifdef CONFIG_USERSPACE + struct flash_stm32_ex_op_sector_wp_in in_copy; + + if (syscall_trap) { + Z_OOPS(z_user_from_copy(&in_copy, request, + sizeof(in_copy))); + request = &in_copy; + } +#endif + change_mask = request->enable_mask; + + if (!IS_ENABLED( + CONFIG_FLASH_STM32_WRITE_PROTECT_DISABLE_PREVENTION)) { + change_mask |= request->disable_mask; + } + + rc = flash_stm32_option_bytes_lock(dev, false); + if (rc == 0) { + rc = flash_stm32_update_wp_sectors( + dev, change_mask, request->enable_mask); + } + + rc2 = flash_stm32_option_bytes_lock(dev, true); + if (!rc) { + rc = rc2; + } + } + + if (result != NULL) { +#ifdef CONFIG_USERSPACE + struct flash_stm32_ex_op_sector_wp_out out_copy; + + if (syscall_trap) { + result = &out_copy; + } +#endif + rc2 = flash_stm32_get_wp_sectors(dev, &result->protected_mask); + if (!rc) { + rc = rc2; + } + +#ifdef CONFIG_USERSPACE + if (syscall_trap) { + Z_OOPS(z_user_to_copy(out, result, sizeof(out_copy))); + } +#endif + } + + return rc; +} +#endif /* CONFIG_FLASH_STM32_WRITE_PROTECT */ + +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION) +int flash_stm32_ex_op_rdp(const struct device *dev, const uintptr_t in, + void *out) +{ + const struct flash_stm32_ex_op_rdp *request = + (const struct flash_stm32_ex_op_rdp *)in; + struct flash_stm32_ex_op_rdp *result = + (struct flash_stm32_ex_op_rdp *)out; + +#ifdef CONFIG_USERSPACE + struct flash_stm32_ex_op_rdp copy; + bool syscall_trap = z_syscall_trap(); +#endif + int rc = 0, rc2 = 0; + + if (request != NULL) { +#ifdef CONFIG_USERSPACE + if (syscall_trap) { + Z_OOPS(z_user_from_copy(©, request, sizeof(copy))); + request = © + } +#endif + rc = flash_stm32_option_bytes_lock(dev, false); + if (rc == 0) { + rc = flash_stm32_update_rdp(dev, request->enable, + request->permanent); + } + + rc2 = flash_stm32_option_bytes_lock(dev, true); + if (!rc) { + rc = rc2; + } + } + + if (result != NULL) { +#ifdef CONFIG_USERSPACE + if (syscall_trap) { + result = © + } +#endif + rc2 = flash_stm32_get_rdp(dev, &result->enable, + &result->permanent); + if (!rc) { + rc = rc2; + } + +#ifdef CONFIG_USERSPACE + if (syscall_trap) { + Z_OOPS(z_user_to_copy(out, result, sizeof(copy))); + } +#endif + } + + return rc; +} +#endif /* CONFIG_FLASH_STM32_READOUT_PROTECTION */ diff --git a/drivers/flash/flash_stm32f4x.c b/drivers/flash/flash_stm32f4x.c index c58ad3ad2a354..2f96390fd6b20 100644 --- a/drivers/flash/flash_stm32f4x.c +++ b/drivers/flash/flash_stm32f4x.c @@ -1,18 +1,24 @@ /* * Copyright (c) 2017 Linaro Limited + * Copyright (c) 2023 Google Inc * * SPDX-License-Identifier: Apache-2.0 */ -#include -#include #include + +#include #include #include +#include +#include + #include #include "flash_stm32.h" +LOG_MODULE_REGISTER(flash_stm32f4x, CONFIG_FLASH_LOG_LEVEL); + bool flash_stm32_valid_range(const struct device *dev, off_t offset, uint32_t len, bool write) @@ -203,6 +209,171 @@ int flash_stm32_write_range(const struct device *dev, unsigned int offset, return rc; } +static __unused int write_optb(const struct device *dev, uint32_t mask, + uint32_t value) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + int rc; + + if (regs->OPTCR & FLASH_OPTCR_OPTLOCK) { + return -EIO; + } + + if ((regs->OPTCR & mask) == value) { + return 0; + } + + rc = flash_stm32_wait_flash_idle(dev); + if (rc < 0) { + return rc; + } + + regs->OPTCR = (regs->OPTCR & ~mask) | value; + regs->OPTCR |= FLASH_OPTCR_OPTSTRT; + + /* Make sure previous write is completed. */ + __DSB(); + + rc = flash_stm32_wait_flash_idle(dev); + if (rc < 0) { + return rc; + } + + return 0; +} + +#if defined(CONFIG_FLASH_STM32_WRITE_PROTECT) +int flash_stm32_update_wp_sectors(const struct device *dev, + uint32_t changed_sectors, + uint32_t protected_sectors) +{ + changed_sectors <<= FLASH_OPTCR_nWRP_Pos; + protected_sectors <<= FLASH_OPTCR_nWRP_Pos; + + if ((changed_sectors & FLASH_OPTCR_nWRP_Msk) != changed_sectors) { + return -EINVAL; + } + + /* Sector is protected when bit == 0. Flip protected_sectors bits */ + protected_sectors = ~protected_sectors & changed_sectors; + + return write_optb(dev, changed_sectors, protected_sectors); +} + +int flash_stm32_get_wp_sectors(const struct device *dev, + uint32_t *protected_sectors) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + + *protected_sectors = + (~regs->OPTCR & FLASH_OPTCR_nWRP_Msk) >> FLASH_OPTCR_nWRP_Pos; + + return 0; +} +#endif /* CONFIG_FLASH_STM32_WRITE_PROTECT */ + +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION) +int flash_stm32_update_rdp(const struct device *dev, bool enable, + bool permanent) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + uint8_t current_level, target_level; + + current_level = + (regs->OPTCR & FLASH_OPTCR_RDP_Msk) >> FLASH_OPTCR_RDP_Pos; + target_level = current_level; + + /* + * 0xAA = RDP level 0 (no protection) + * 0xCC = RDP level 2 (permanent protection) + * others = RDP level 1 (protection active) + */ + switch (current_level) { + case FLASH_STM32_RDP2: + if (!enable || !permanent) { + __ASSERT(false, "RDP level 2 is permanent and can't be " + "changed!"); + return -ENOTSUP; + } + break; + case FLASH_STM32_RDP0: + if (enable) { + target_level = FLASH_STM32_RDP1; + if (permanent) { +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION_PERMANENT_ALLOW) + target_level = FLASH_STM32_RDP2; +#else + __ASSERT(false, + "Permanent readout protection (RDP " + "level 0 -> 2) not allowed"); + return -ENOTSUP; +#endif + } + } + break; + default: /* FLASH_STM32_RDP1 */ + if (enable && permanent) { +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION_PERMANENT_ALLOW) + target_level = FLASH_STM32_RDP2; +#else + __ASSERT(false, "Permanent readout protection (RDP " + "level 1 -> 2) not allowed"); + return -ENOTSUP; +#endif + } + if (!enable) { +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION_DISABLE_ALLOW) + target_level = FLASH_STM32_RDP0; +#else + __ASSERT(false, "Disabling readout protection (RDP " + "level 1 -> 0) not allowed"); + return -EACCES; +#endif + } + } + + /* Update RDP level if needed */ + if (current_level != target_level) { + LOG_INF("RDP changed from 0x%02x to 0x%02x", current_level, + target_level); + + write_optb(dev, FLASH_OPTCR_RDP_Msk, + (uint32_t)target_level << FLASH_OPTCR_RDP_Pos); + } + return 0; +} + +int flash_stm32_get_rdp(const struct device *dev, bool *enabled, + bool *permanent) +{ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); + uint8_t current_level; + + current_level = + (regs->OPTCR & FLASH_OPTCR_RDP_Msk) >> FLASH_OPTCR_RDP_Pos; + + /* + * 0xAA = RDP level 0 (no protection) + * 0xCC = RDP level 2 (permanent protection) + * others = RDP level 1 (protection active) + */ + switch (current_level) { + case FLASH_STM32_RDP2: + *enabled = true; + *permanent = true; + break; + case FLASH_STM32_RDP0: + *enabled = false; + *permanent = false; + break; + default: /* FLASH_STM32_RDP1 */ + *enabled = true; + *permanent = false; + } + return 0; +} +#endif /* CONFIG_FLASH_STM32_READOUT_PROTECTION */ + /* * Different SoC flash layouts are specified in across various * reference manuals, but the flash layout for a given number of diff --git a/dts/bindings/flash_controller/st,stm32-flash-controller.yaml b/dts/bindings/flash_controller/st,stm32-flash-controller.yaml index 5156f4be3595d..34a28036e4228 100644 --- a/dts/bindings/flash_controller/st,stm32-flash-controller.yaml +++ b/dts/bindings/flash_controller/st,stm32-flash-controller.yaml @@ -3,3 +3,15 @@ description: STM32 Family flash controller compatible: "st,stm32-flash-controller" include: flash-controller.yaml + +properties: + st,rdp1-enable-byte: + type: int + default: 0x55 + description: | + This property provides a byte which should used to enable non-permanent + readout protection (RDP1). Actually, any byte except 0xAA and 0xCC + (which are used by RDP0 and RDP2 respectively) can be used to enable + RDP1 but in multi-image environment, some other image could check if + RDP1 is enabled by comparing it to some hardcoded value. The byte has to + be different than 0xAA and 0xCC. diff --git a/include/zephyr/drivers/flash/stm32_flash_api_extensions.h b/include/zephyr/drivers/flash/stm32_flash_api_extensions.h new file mode 100644 index 0000000000000..0c6c8fc88ad14 --- /dev/null +++ b/include/zephyr/drivers/flash/stm32_flash_api_extensions.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 Google Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +enum stm32_ex_ops { + /* + * STM32 sector write protection control. + * + * As an input this operation takes a structure with two sector masks, + * first mask is used to enable protection on sectors, while second mask + * is used to do the opposite. If both masks are 0, then protection will + * remain unchanged. If same sector is set on both masks, protection + * will be enabled. + * + * As an output, sector mask with enabled protection is returned. + * Input can be NULL if we only want to get protected sectors. + * Output can be NULL if not needed. + */ + FLASH_STM32_EX_OP_SECTOR_WP = FLASH_EX_OP_VENDOR_BASE, + /* + * STM32 sector readout protection control. + * + * As an input this operation takes structure with information about + * desired RDP state. As an output the status after applying changes + * is returned. + */ + FLASH_STM32_EX_OP_RDP, + /* + * STM32 block option register. + * + * This operation causes option register to be locked until next boot. + * After calling, it's not possible to change option bytes (WP, RDP, + * user bytes). + */ + FLASH_STM32_EX_OP_BLOCK_OPTION_REG, + /* + * STM32 block control register. + * + * This operation causes control register to be locked until next boot. + * After calling, it's not possible to perform basic operation like + * erasing or writing. + */ + FLASH_STM32_EX_OP_BLOCK_CONTROL_REG, +}; + +#if defined(CONFIG_FLASH_STM32_WRITE_PROTECT) +struct flash_stm32_ex_op_sector_wp_in { + uint32_t enable_mask; + uint32_t disable_mask; +}; + +struct flash_stm32_ex_op_sector_wp_out { + uint32_t protected_mask; +}; +#endif /* CONFIG_FLASH_STM32_WRITE_PROTECT */ + +#if defined(CONFIG_FLASH_STM32_READOUT_PROTECTION) +struct flash_stm32_ex_op_rdp { + bool enable; + bool permanent; +}; +#endif /* CONFIG_FLASH_STM32_READOUT_PROTECTION */ diff --git a/tests/drivers/flash/CMakeLists.txt b/tests/drivers/flash/common/CMakeLists.txt similarity index 100% rename from tests/drivers/flash/CMakeLists.txt rename to tests/drivers/flash/common/CMakeLists.txt diff --git a/tests/drivers/flash/boards/b_u585i_iot02a_ns.overlay b/tests/drivers/flash/common/boards/b_u585i_iot02a_ns.overlay similarity index 100% rename from tests/drivers/flash/boards/b_u585i_iot02a_ns.overlay rename to tests/drivers/flash/common/boards/b_u585i_iot02a_ns.overlay diff --git a/tests/drivers/flash/boards/disco_l475_iot1.conf b/tests/drivers/flash/common/boards/disco_l475_iot1.conf similarity index 100% rename from tests/drivers/flash/boards/disco_l475_iot1.conf rename to tests/drivers/flash/common/boards/disco_l475_iot1.conf diff --git a/tests/drivers/flash/boards/esp32c3_devkitm.conf b/tests/drivers/flash/common/boards/esp32c3_devkitm.conf similarity index 100% rename from tests/drivers/flash/boards/esp32c3_devkitm.conf rename to tests/drivers/flash/common/boards/esp32c3_devkitm.conf diff --git a/tests/drivers/flash/boards/icev_wireless.conf b/tests/drivers/flash/common/boards/icev_wireless.conf similarity index 100% rename from tests/drivers/flash/boards/icev_wireless.conf rename to tests/drivers/flash/common/boards/icev_wireless.conf diff --git a/tests/drivers/flash/boards/mimxrt1060_evk.conf b/tests/drivers/flash/common/boards/mimxrt1060_evk.conf similarity index 100% rename from tests/drivers/flash/boards/mimxrt1060_evk.conf rename to tests/drivers/flash/common/boards/mimxrt1060_evk.conf diff --git a/tests/drivers/flash/boards/mimxrt595_evk_cm33.conf b/tests/drivers/flash/common/boards/mimxrt595_evk_cm33.conf similarity index 100% rename from tests/drivers/flash/boards/mimxrt595_evk_cm33.conf rename to tests/drivers/flash/common/boards/mimxrt595_evk_cm33.conf diff --git a/tests/drivers/flash/boards/mimxrt685_evk_cm33.conf b/tests/drivers/flash/common/boards/mimxrt685_evk_cm33.conf similarity index 100% rename from tests/drivers/flash/boards/mimxrt685_evk_cm33.conf rename to tests/drivers/flash/common/boards/mimxrt685_evk_cm33.conf diff --git a/tests/drivers/flash/boards/nrf52840_flash_qspi.conf b/tests/drivers/flash/common/boards/nrf52840_flash_qspi.conf similarity index 100% rename from tests/drivers/flash/boards/nrf52840_flash_qspi.conf rename to tests/drivers/flash/common/boards/nrf52840_flash_qspi.conf diff --git a/tests/drivers/flash/boards/nrf52840_flash_soc.conf b/tests/drivers/flash/common/boards/nrf52840_flash_soc.conf similarity index 100% rename from tests/drivers/flash/boards/nrf52840_flash_soc.conf rename to tests/drivers/flash/common/boards/nrf52840_flash_soc.conf diff --git a/tests/drivers/flash/boards/nrf52840_size_in_bytes.overlay b/tests/drivers/flash/common/boards/nrf52840_size_in_bytes.overlay similarity index 100% rename from tests/drivers/flash/boards/nrf52840_size_in_bytes.overlay rename to tests/drivers/flash/common/boards/nrf52840_size_in_bytes.overlay diff --git a/tests/drivers/flash/boards/nrf52840dk_flash_spi.conf b/tests/drivers/flash/common/boards/nrf52840dk_flash_spi.conf similarity index 100% rename from tests/drivers/flash/boards/nrf52840dk_flash_spi.conf rename to tests/drivers/flash/common/boards/nrf52840dk_flash_spi.conf diff --git a/tests/drivers/flash/boards/nrf52840dk_mx25l51245g.overlay b/tests/drivers/flash/common/boards/nrf52840dk_mx25l51245g.overlay similarity index 100% rename from tests/drivers/flash/boards/nrf52840dk_mx25l51245g.overlay rename to tests/drivers/flash/common/boards/nrf52840dk_mx25l51245g.overlay diff --git a/tests/drivers/flash/boards/nrf52840dk_mx25r_high_perf.overlay b/tests/drivers/flash/common/boards/nrf52840dk_mx25r_high_perf.overlay similarity index 100% rename from tests/drivers/flash/boards/nrf52840dk_mx25r_high_perf.overlay rename to tests/drivers/flash/common/boards/nrf52840dk_mx25r_high_perf.overlay diff --git a/tests/drivers/flash/boards/xmc45_relax_kit.conf b/tests/drivers/flash/common/boards/xmc45_relax_kit.conf similarity index 100% rename from tests/drivers/flash/boards/xmc45_relax_kit.conf rename to tests/drivers/flash/common/boards/xmc45_relax_kit.conf diff --git a/tests/drivers/flash/prj.conf b/tests/drivers/flash/common/prj.conf similarity index 100% rename from tests/drivers/flash/prj.conf rename to tests/drivers/flash/common/prj.conf diff --git a/tests/drivers/flash/src/main.c b/tests/drivers/flash/common/src/main.c similarity index 100% rename from tests/drivers/flash/src/main.c rename to tests/drivers/flash/common/src/main.c diff --git a/tests/drivers/flash/testcase.yaml b/tests/drivers/flash/common/testcase.yaml similarity index 87% rename from tests/drivers/flash/testcase.yaml rename to tests/drivers/flash/common/testcase.yaml index ac3a5affec93c..a24fad02cc19e 100644 --- a/tests/drivers/flash/testcase.yaml +++ b/tests/drivers/flash/common/testcase.yaml @@ -1,19 +1,19 @@ common: tags: drivers flash tests: - drivers.flash.nrf_qspi_nor: + drivers.flash.common.nrf_qspi_nor: platform_allow: nrf52840dk_nrf52840 extra_args: OVERLAY_CONFIG=boards/nrf52840_flash_qspi.conf integration_platforms: - nrf52840dk_nrf52840 - drivers.flash.nrf_qspi_nor.size_in_bytes: + drivers.flash.common.nrf_qspi_nor.size_in_bytes: platform_allow: nrf52840dk_nrf52840 extra_args: OVERLAY_CONFIG=boards/nrf52840_flash_qspi.conf DTC_OVERLAY_FILE=boards/nrf52840_size_in_bytes.overlay integration_platforms: - nrf52840dk_nrf52840 - drivers.flash.nrf_qspi_nor_4B_addr: + drivers.flash.common.nrf_qspi_nor_4B_addr: platform_allow: nrf52840dk_nrf52840 extra_args: OVERLAY_CONFIG=boards/nrf52840_flash_qspi.conf DTC_OVERLAY_FILE=boards/nrf52840dk_mx25l51245g.overlay @@ -21,16 +21,16 @@ tests: fixture: external_flash integration_platforms: - nrf52840dk_nrf52840 - drivers.flash.soc_flash_nrf: + drivers.flash.common.soc_flash_nrf: platform_allow: nrf52840dk_nrf52840 extra_args: OVERLAY_CONFIG=boards/nrf52840_flash_soc.conf integration_platforms: - nrf52840dk_nrf52840 - drivers.flash.default: + drivers.flash.common.default: filter: ((CONFIG_FLASH_HAS_DRIVER_ENABLED and not CONFIG_TRUSTED_EXECUTION_NONSECURE) and dt_label_with_parent_compat_enabled("storage_partition", "fixed-partitions")) - drivers.flash.tfm_ns: + drivers.flash.common.tfm_ns: build_only: true filter: (CONFIG_FLASH_HAS_DRIVER_ENABLED and CONFIG_TRUSTED_EXECUTION_NONSECURE and dt_label_with_parent_compat_enabled("slot1_ns_partition", "fixed-partitions")) @@ -38,7 +38,7 @@ tests: platform_allow: mimxrt1060_evk it8xxx2_evb mimxrt685_evk_cm33 mimxrt595_evk_cm33 integration_platforms: - mimxrt1060_evk - drivers.flash.stm32: + drivers.flash.common.stm32: platform_allow: nucleo_f103rb nucleo_f207zg stm32f3_disco nucleo_f429zi stm32f746g_disco nucleo_g0b1re nucleo_g474re nucleo_h743zi nucleo_l152re disco_l475_iot1 nucleo_wb55rg nucleo_wl55jc stm32l562e_dk stm32l562e_dk_ns @@ -47,7 +47,7 @@ tests: filter: (dt_compat_enabled("st,stm32-flash-controller") or dt_compat_enabled("st,stm32h7-flash-controller")) and dt_label_with_parent_compat_enabled("storage_partition", "fixed-partitions") - drivers.flash.mx25r_high_perf: + drivers.flash.common.mx25r_high_perf: platform_allow: nrf52840dk_nrf52840 extra_args: OVERLAY_CONFIG=boards/nrf52840dk_flash_spi.conf diff --git a/tests/drivers/flash/stm32/CMakeLists.txt b/tests/drivers/flash/stm32/CMakeLists.txt new file mode 100644 index 0000000000000..2d8bb41ee9a84 --- /dev/null +++ b/tests/drivers/flash/stm32/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(stm32_extended_operations) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/flash/stm32/boards/google_dragonclaw.overlay b/tests/drivers/flash/stm32/boards/google_dragonclaw.overlay new file mode 100644 index 0000000000000..e9bbc1b87bf12 --- /dev/null +++ b/tests/drivers/flash/stm32/boards/google_dragonclaw.overlay @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Google Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* Reserve last 4KiB of flash for storage_partition. */ + storage_partition: partition@ff000 { + label = "storage"; + reg = <0x000ff000 DT_SIZE_K(4)>; + }; + }; +}; diff --git a/tests/drivers/flash/stm32/prj.conf b/tests/drivers/flash/stm32/prj.conf new file mode 100644 index 0000000000000..4f43063f0cd7c --- /dev/null +++ b/tests/drivers/flash/stm32/prj.conf @@ -0,0 +1,11 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y + +CONFIG_FLASH=y +CONFIG_FLASH_EX_OP_ENABLED=y +CONFIG_FLASH_STM32_READOUT_PROTECTION=y +CONFIG_FLASH_STM32_WRITE_PROTECT=y +CONFIG_FLASH_STM32_BLOCK_REGISTERS=y + +CONFIG_MAIN_STACK_SIZE=2048 diff --git a/tests/drivers/flash/stm32/src/main.c b/tests/drivers/flash/stm32/src/main.c new file mode 100644 index 0000000000000..b5770dd6b7fbd --- /dev/null +++ b/tests/drivers/flash/stm32/src/main.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2023 Google Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define TEST_AREA storage_partition +#define TEST_AREA_OFFSET FIXED_PARTITION_OFFSET(TEST_AREA) +#define TEST_AREA_SIZE FIXED_PARTITION_SIZE(TEST_AREA) +#define TEST_AREA_MAX (TEST_AREA_OFFSET + TEST_AREA_SIZE) +#define TEST_AREA_DEVICE FIXED_PARTITION_DEVICE(TEST_AREA) + +#define EXPECTED_SIZE 512 + +static const struct device *const flash_dev = TEST_AREA_DEVICE; +static const struct flash_parameters *flash_params; +static uint32_t sector_mask; +static uint8_t __aligned(4) expected[EXPECTED_SIZE]; + +static int sector_mask_from_offset(const struct device *dev, off_t offset, + size_t size, uint32_t *sector_mask) +{ + struct flash_pages_info start_page, end_page; + + if (flash_get_page_info_by_offs(dev, offset, &start_page) || + flash_get_page_info_by_offs(dev, offset + size - 1, &end_page)) { + return -EINVAL; + } + + *sector_mask = ((1UL << (end_page.index + 1)) - 1) & + ~((1UL << start_page.index) - 1); + + return 0; +} + +static void *flash_stm32_setup(void) +{ + struct flash_stm32_ex_op_sector_wp_out wp_status; + struct flash_stm32_ex_op_sector_wp_in wp_request; + uint8_t buf[EXPECTED_SIZE]; + bool is_buf_clear = true; + int rc; + + /* Check if tested region fits in flash. */ + zassert_true((TEST_AREA_OFFSET + EXPECTED_SIZE) < TEST_AREA_MAX, + "Test area exceeds flash size"); + + zassert_true(device_is_ready(flash_dev)); + + flash_params = flash_get_parameters(flash_dev); + + rc = sector_mask_from_offset(flash_dev, TEST_AREA_OFFSET, EXPECTED_SIZE, + §or_mask); + zassert_equal(rc, 0, "Cannot get sector mask"); + + TC_PRINT("Sector mask for offset 0x%x size 0x%x is 0x%x\n", + TEST_AREA_OFFSET, EXPECTED_SIZE, sector_mask); + + /* Check if region is not write protected. */ + rc = flash_ex_op(flash_dev, FLASH_STM32_EX_OP_SECTOR_WP, + (uintptr_t)NULL, &wp_status); + zassert_equal(rc, 0, "Cannot get write protect status"); + + TC_PRINT("Protected sectors: 0x%x\n", wp_status.protected_mask); + + /* Remove protection if needed. */ + if (wp_status.protected_mask & sector_mask) { + TC_PRINT("Removing write protection\n"); + wp_request.disable_mask = sector_mask; + + rc = flash_ex_op(flash_dev, FLASH_STM32_EX_OP_SECTOR_WP, + (uintptr_t)&wp_request, NULL); + zassert_equal(rc, 0, "Cannot remove write protection"); + } + + /* Check if test region is not empty. */ + rc = flash_read(flash_dev, TEST_AREA_OFFSET, buf, EXPECTED_SIZE); + zassert_equal(rc, 0, "Cannot read flash"); + + /* Check if flash is cleared. */ + for (off_t i = 0; i < EXPECTED_SIZE; i++) { + if (buf[i] != flash_params->erase_value) { + is_buf_clear = false; + break; + } + } + + if (!is_buf_clear) { + TC_PRINT("Test area is not empty. Clear it before continuing.\n"); + rc = flash_erase(flash_dev, TEST_AREA_OFFSET, EXPECTED_SIZE); + zassert_equal(rc, 0, "Flash memory not properly erased"); + } + + /* Fill test buffer with some pattern. */ + for (int i = 0; i < EXPECTED_SIZE; i++) { + expected[i] = i; + } + + return NULL; +} + +ZTEST(flash_stm32, test_stm32_write_protection) +{ + struct flash_stm32_ex_op_sector_wp_in wp_request; + uint8_t buf[EXPECTED_SIZE]; + int rc; + + TC_PRINT("Enabling write protection..."); + wp_request.disable_mask = 0; + wp_request.enable_mask = sector_mask; + + rc = flash_ex_op(flash_dev, FLASH_STM32_EX_OP_SECTOR_WP, + (uintptr_t)&wp_request, NULL); + zassert_equal(rc, 0, "Cannot enable write protection"); + TC_PRINT("Done\n"); + + rc = flash_write(flash_dev, TEST_AREA_OFFSET, expected, EXPECTED_SIZE); + zassert_not_equal(rc, 0, "Write suceeded"); + TC_PRINT("Write failed as expected, error %d\n", rc); + + rc = flash_read(flash_dev, TEST_AREA_OFFSET, buf, EXPECTED_SIZE); + zassert_equal(rc, 0, "Cannot read flash"); + + for (off_t i = 0; i < EXPECTED_SIZE; i++) { + zassert_true(buf[i] == flash_params->erase_value, + "Buffer is not empty after write with protected " + "sectors"); + } + + TC_PRINT("Disabling write protection..."); + wp_request.disable_mask = sector_mask; + wp_request.enable_mask = 0; + + rc = flash_ex_op(flash_dev, FLASH_STM32_EX_OP_SECTOR_WP, + (uintptr_t)&wp_request, NULL); + zassert_equal(rc, 0, "Cannot disable write protection"); + TC_PRINT("Done\n"); + + rc = flash_write(flash_dev, TEST_AREA_OFFSET, expected, EXPECTED_SIZE); + zassert_equal(rc, 0, "Write failed"); + TC_PRINT("Write suceeded\n"); + + rc = flash_read(flash_dev, TEST_AREA_OFFSET, buf, EXPECTED_SIZE); + zassert_equal(rc, 0, "Cannot read flash"); + + zassert_equal(memcmp(buf, expected, EXPECTED_SIZE), 0, + "Read data doesn't match expected data"); +} + +ZTEST(flash_stm32, test_stm32_readout_protection_disabled) +{ + struct flash_stm32_ex_op_rdp rdp_status; + int rc; + + rc = flash_ex_op(flash_dev, FLASH_STM32_EX_OP_RDP, (uintptr_t)NULL, + &rdp_status); + zassert_equal(rc, 0, "Failed to get RDP status"); + zassert_false(rdp_status.enable, "RDP is enabled"); + zassert_false(rdp_status.permanent, "RDP is enabled permanently"); + + TC_PRINT("RDP is disabled\n"); +} + +ZTEST_SUITE(flash_stm32, NULL, flash_stm32_setup, NULL, NULL, NULL); diff --git a/tests/drivers/flash/stm32/testcase.yaml b/tests/drivers/flash/stm32/testcase.yaml new file mode 100644 index 0000000000000..a438934bd75f9 --- /dev/null +++ b/tests/drivers/flash/stm32/testcase.yaml @@ -0,0 +1,7 @@ +common: + tags: drivers flash +tests: + drivers.flash.stm32.default: + platform_allow: nucleo_f429zi google_dragonclaw + filter: dt_compat_enabled("st,stm32-flash-controller") and + dt_label_with_parent_compat_enabled("storage_partition", "fixed-partitions")