From f083570af625d28c7474c596884f21b2dd79ece5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20M=C3=BAdry?= Date: Thu, 7 Sep 2023 15:59:47 +0200 Subject: [PATCH 1/3] refactor(wear_levelling): WL_Flash flash_drv pointer changed to partition pointer --- components/wear_levelling/Partition.cpp | 7 +- components/wear_levelling/WL_Ext_Perf.cpp | 7 +- components/wear_levelling/WL_Ext_Safe.cpp | 4 +- components/wear_levelling/WL_Flash.cpp | 101 +++++++++--------- .../private_include/Partition.h | 1 + .../private_include/WL_Ext_Perf.h | 3 +- .../private_include/WL_Ext_Safe.h | 3 +- .../wear_levelling/private_include/WL_Flash.h | 9 +- components/wear_levelling/wear_levelling.cpp | 8 +- 9 files changed, 77 insertions(+), 66 deletions(-) diff --git a/components/wear_levelling/Partition.cpp b/components/wear_levelling/Partition.cpp index a4618053f4b9..c97f45ba6031 100644 --- a/components/wear_levelling/Partition.cpp +++ b/components/wear_levelling/Partition.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -54,6 +54,11 @@ size_t Partition::get_sector_size() return SPI_FLASH_SEC_SIZE; } +bool Partition::is_readonly() +{ + return this->partition->readonly; +} + Partition::~Partition() { diff --git a/components/wear_levelling/WL_Ext_Perf.cpp b/components/wear_levelling/WL_Ext_Perf.cpp index 3e7c137a3fce..03fbfdce7e0a 100644 --- a/components/wear_levelling/WL_Ext_Perf.cpp +++ b/components/wear_levelling/WL_Ext_Perf.cpp @@ -1,9 +1,10 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "WL_Ext_Perf.h" +#include "Partition.h" #include #include "esp_log.h" @@ -25,7 +26,7 @@ WL_Ext_Perf::~WL_Ext_Perf() free(this->sector_buffer); } -esp_err_t WL_Ext_Perf::config(WL_Config_s *cfg, Flash_Access *flash_drv) +esp_err_t WL_Ext_Perf::config(WL_Config_s *cfg, Partition *partition) { wl_ext_cfg_t *ext_cfg = (wl_ext_cfg_t *)cfg; @@ -44,7 +45,7 @@ esp_err_t WL_Ext_Perf::config(WL_Config_s *cfg, Flash_Access *flash_drv) return ESP_ERR_NO_MEM; } - return WL_Flash::config(cfg, flash_drv); + return WL_Flash::config(cfg, partition); } esp_err_t WL_Ext_Perf::init() diff --git a/components/wear_levelling/WL_Ext_Safe.cpp b/components/wear_levelling/WL_Ext_Safe.cpp index bb9ad3beaccf..695b6829ce12 100644 --- a/components/wear_levelling/WL_Ext_Safe.cpp +++ b/components/wear_levelling/WL_Ext_Safe.cpp @@ -49,11 +49,11 @@ WL_Ext_Safe::~WL_Ext_Safe() { } -esp_err_t WL_Ext_Safe::config(WL_Config_s *cfg, Flash_Access *flash_drv) +esp_err_t WL_Ext_Safe::config(WL_Config_s *cfg, Partition *partition) { esp_err_t result = ESP_OK; - result = WL_Ext_Perf::config(cfg, flash_drv); + result = WL_Ext_Perf::config(cfg, partition); WL_EXT_RESULT_CHECK(result); /* two extra sectors will be reserved to store buffer transaction state WL_Ext_Safe_State and temporary storage of the actual sector data from the sector which is to be erased*/ diff --git a/components/wear_levelling/WL_Flash.cpp b/components/wear_levelling/WL_Flash.cpp index aa15bfc95954..d82b250b9f0b 100644 --- a/components/wear_levelling/WL_Flash.cpp +++ b/components/wear_levelling/WL_Flash.cpp @@ -6,6 +6,7 @@ #include #include "esp_random.h" #include "esp_log.h" +#include "Partition.h" #include "WL_Flash.h" #include #include "crc32.h" @@ -37,7 +38,7 @@ WL_Flash::~WL_Flash() free(this->temp_buff); } -esp_err_t WL_Flash::config(wl_config_t *cfg, Flash_Access *flash_drv) +esp_err_t WL_Flash::config(wl_config_t *cfg, Partition *partition) { ESP_LOGV(TAG, "%s partition_start_addr=0x%08x, wl_partition_size=0x%08x, wl_page_size=0x%08x, flash_sector_size=0x%08x, wl_update_rate=0x%08x, wl_pos_update_record_size=0x%08x, version=0x%08x, wl_temp_buff_size=0x%08x", __func__, (uint32_t) cfg->wl_partition_start_addr, @@ -59,8 +60,8 @@ esp_err_t WL_Flash::config(wl_config_t *cfg, Flash_Access *flash_drv) if (cfg == NULL) { result = ESP_ERR_INVALID_ARG; } - this->flash_drv = flash_drv; - if (flash_drv == NULL) { + this->partition = partition; + if (partition == NULL) { result = ESP_ERR_INVALID_ARG; } if ((this->cfg.flash_sector_size % this->cfg.wl_temp_buff_size) != 0) { @@ -118,10 +119,10 @@ esp_err_t WL_Flash::init() // If flow will be interrupted by error, then this flag will be false this->initialized = false; // Init states if it is first time... - this->flash_drv->read(this->addr_state1, &this->state, sizeof(wl_state_t)); + this->partition->read(this->addr_state1, &this->state, sizeof(wl_state_t)); wl_state_t sa_copy; wl_state_t *state_copy = &sa_copy; - result = this->flash_drv->read(this->addr_state2, state_copy, sizeof(wl_state_t)); + result = this->partition->read(this->addr_state2, state_copy, sizeof(wl_state_t)); WL_RESULT_CHECK(result); int check_size = WL_STATE_CRC_LEN_V2; @@ -149,19 +150,19 @@ esp_err_t WL_Flash::init() WL_RESULT_CHECK(result); } else { if (crc1 != crc2) {// we did not update second structure. - result = this->flash_drv->erase_range(this->addr_state2, this->state_size); + result = this->partition->erase_range(this->addr_state2, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state2, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); for (size_t i = 0; i < ((this->cfg.wl_partition_size / this->cfg.flash_sector_size)); i++) { bool pos_bits; - result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); pos_bits = this->OkBuffSet(i); if (pos_bits == true) { //this->fillOkBuff(i); - result = this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->write(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); } } @@ -184,41 +185,41 @@ esp_err_t WL_Flash::init() } else { // recover broken state if (crc1 == this->state.crc32) {// we have to recover state 2 - result = this->flash_drv->erase_range(this->addr_state2, this->state_size); + result = this->partition->erase_range(this->addr_state2, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state2, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); for (size_t i = 0; i < ((this->cfg.wl_partition_size / this->cfg.flash_sector_size)); i++) { bool pos_bits; - result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); pos_bits = this->OkBuffSet(i); if (pos_bits == true) { - result = this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->write(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); } } - result = this->flash_drv->read(this->addr_state2, &this->state, sizeof(wl_state_t)); + result = this->partition->read(this->addr_state2, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); } else { // we have to recover state 1 - result = this->flash_drv->erase_range(this->addr_state1, this->state_size); + result = this->partition->erase_range(this->addr_state1, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state1, state_copy, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state1, state_copy, sizeof(wl_state_t)); WL_RESULT_CHECK(result); for (size_t i = 0; i < ((this->cfg.wl_partition_size / this->cfg.flash_sector_size)); i++) { bool pos_bits; - result = this->flash_drv->read(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->read(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); pos_bits = this->OkBuffSet(i); if (pos_bits == true) { - result = this->flash_drv->write(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->write(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); } } - result = this->flash_drv->read(this->addr_state1, &this->state, sizeof(wl_state_t)); + result = this->partition->read(this->addr_state1, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); this->state.wl_dummy_sec_pos = this->state.wl_part_max_sec_pos - 1; } @@ -247,7 +248,7 @@ esp_err_t WL_Flash::recoverPos() for (size_t i = 0; i < this->state.wl_part_max_sec_pos; i++) { bool pos_bits; position = i; - result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); pos_bits = this->OkBuffSet(i); WL_RESULT_CHECK(result); ESP_LOGV(TAG, "%s - check pos: result=0x%08x, position= %i, pos_bits= 0x%08x", __func__, (uint32_t)result, (uint32_t)position, (uint32_t)pos_bits); @@ -285,19 +286,19 @@ esp_err_t WL_Flash::initSections() this->state.crc32 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, WL_STATE_CRC_LEN_V2); - result = this->flash_drv->erase_range(this->addr_state1, this->state_size); + result = this->partition->erase_range(this->addr_state1, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state1, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state1, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); // write state copy - result = this->flash_drv->erase_range(this->addr_state2, this->state_size); + result = this->partition->erase_range(this->addr_state2, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state2, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); - result = this->flash_drv->erase_range(this->addr_cfg, this->cfg_size); + result = this->partition->erase_range(this->addr_cfg, this->cfg_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_cfg, &this->cfg, sizeof(wl_config_t)); + result = this->partition->write(this->addr_cfg, &this->cfg, sizeof(wl_config_t)); WL_RESULT_CHECK(result); ESP_LOGD(TAG, "%s - this->state->wl_max_sec_erase_cycle_count= 0x%08x, this->state->wl_part_max_sec_pos= 0x%08x", __func__, this->state.wl_max_sec_erase_cycle_count, this->state.wl_part_max_sec_pos); @@ -327,7 +328,7 @@ esp_err_t WL_Flash::updateV1_V2() uint32_t crc1 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, check_size); wl_state_t sa_copy; wl_state_t *state_copy = &sa_copy; - result = this->flash_drv->read(this->addr_state2, state_copy, sizeof(wl_state_t)); + result = this->partition->read(this->addr_state2, state_copy, sizeof(wl_state_t)); WL_RESULT_CHECK(result); uint32_t crc2 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)state_copy, check_size); @@ -344,7 +345,7 @@ esp_err_t WL_Flash::updateV1_V2() for (size_t i = 0; i < this->state.wl_part_max_sec_pos; i++) { uint8_t pos_bits; - result = this->flash_drv->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, &pos_bits, 1); + result = this->partition->read(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, &pos_bits, 1); WL_RESULT_CHECK(result); ESP_LOGV(TAG, "%s- result= 0x%08x, pos= %i, pos_bits= 0x%08x", __func__, (uint32_t)result, (uint32_t)pos, (uint32_t)pos_bits); pos = i; @@ -364,28 +365,28 @@ esp_err_t WL_Flash::updateV1_V2() memset(this->state.reserved, 0, sizeof(this->state.reserved)); this->state.crc32 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, WL_STATE_CRC_LEN_V2); - result = this->flash_drv->erase_range(this->addr_state1, this->state_size); + result = this->partition->erase_range(this->addr_state1, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state1, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state1, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); memset(this->temp_buff, 0, this->cfg.wl_pos_update_record_size); for (uint32_t i = 0 ; i <= pos; i++) { this->fillOkBuff(i); - result = this->flash_drv->write(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->write(this->addr_state1 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); } - result = this->flash_drv->erase_range(this->addr_state2, this->state_size); + result = this->partition->erase_range(this->addr_state2, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state2, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); ESP_LOGD(TAG, "%s - wl_dummy_sec_move_count= 0x%08x, pos= 0x%08x", __func__, this->state.wl_dummy_sec_move_count, this->state.wl_dummy_sec_pos); memset(this->temp_buff, 0, this->cfg.wl_pos_update_record_size); for (uint32_t i = 0 ; i <= pos; i++) { this->fillOkBuff(i); - result = this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); + result = this->partition->write(this->addr_state2 + sizeof(wl_state_t) + i * this->cfg.wl_pos_update_record_size, this->temp_buff, this->cfg.wl_pos_update_record_size); WL_RESULT_CHECK(result); } this->state.wl_dummy_sec_pos = pos; @@ -437,7 +438,7 @@ esp_err_t WL_Flash::updateWL() } data_addr = this->cfg.wl_partition_start_addr + data_addr * this->cfg.wl_page_size; this->dummy_addr = this->cfg.wl_partition_start_addr + this->state.wl_dummy_sec_pos * this->cfg.wl_page_size; - result = this->flash_drv->erase_range(this->dummy_addr, this->cfg.wl_page_size); + result = this->partition->erase_range(this->dummy_addr, this->cfg.wl_page_size); if (result != ESP_OK) { ESP_LOGE(TAG, "%s - erase wl dummy sector result= 0x%08x", __func__, result); this->state.wl_sec_erase_cycle_count = this->state.wl_max_sec_erase_cycle_count - 1; // we will update next time @@ -446,13 +447,13 @@ esp_err_t WL_Flash::updateWL() size_t copy_count = this->cfg.wl_page_size / this->cfg.wl_temp_buff_size; for (size_t i = 0; i < copy_count; i++) { - result = this->flash_drv->read(data_addr + i * this->cfg.wl_temp_buff_size, this->temp_buff, this->cfg.wl_temp_buff_size); + result = this->partition->read(data_addr + i * this->cfg.wl_temp_buff_size, this->temp_buff, this->cfg.wl_temp_buff_size); if (result != ESP_OK) { ESP_LOGE(TAG, "%s - not possible to read buffer, will try next time, result= 0x%08x", __func__, result); this->state.wl_sec_erase_cycle_count = this->state.wl_max_sec_erase_cycle_count - 1; // we will update next time return result; } - result = this->flash_drv->write(this->dummy_addr + i * this->cfg.wl_temp_buff_size, this->temp_buff, this->cfg.wl_temp_buff_size); + result = this->partition->write(this->dummy_addr + i * this->cfg.wl_temp_buff_size, this->temp_buff, this->cfg.wl_temp_buff_size); if (result != ESP_OK) { ESP_LOGE(TAG, "%s - not possible to write buffer, will try next time, result= 0x%08x", __func__, result); this->state.wl_sec_erase_cycle_count = this->state.wl_max_sec_erase_cycle_count - 1; // we will update next time @@ -465,14 +466,14 @@ esp_err_t WL_Flash::updateWL() uint32_t byte_pos = this->state.wl_dummy_sec_pos * this->cfg.wl_pos_update_record_size; this->fillOkBuff(this->state.wl_dummy_sec_pos); // write state to mem. We updating only affected bits - result |= this->flash_drv->write(this->addr_state1 + sizeof(wl_state_t) + byte_pos, this->temp_buff, this->cfg.wl_pos_update_record_size); + result |= this->partition->write(this->addr_state1 + sizeof(wl_state_t) + byte_pos, this->temp_buff, this->cfg.wl_pos_update_record_size); if (result != ESP_OK) { ESP_LOGE(TAG, "%s - update position 1 result= 0x%08x", __func__, result); this->state.wl_sec_erase_cycle_count = this->state.wl_max_sec_erase_cycle_count - 1; // we will update next time return result; } this->fillOkBuff(this->state.wl_dummy_sec_pos); - result |= this->flash_drv->write(this->addr_state2 + sizeof(wl_state_t) + byte_pos, this->temp_buff, this->cfg.wl_pos_update_record_size); + result |= this->partition->write(this->addr_state2 + sizeof(wl_state_t) + byte_pos, this->temp_buff, this->cfg.wl_pos_update_record_size); if (result != ESP_OK) { ESP_LOGE(TAG, "%s - update position 2 result= 0x%08x", __func__, result); this->state.wl_sec_erase_cycle_count = this->state.wl_max_sec_erase_cycle_count - 1; // we will update next time @@ -490,13 +491,13 @@ esp_err_t WL_Flash::updateWL() // write main state this->state.crc32 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)&this->state, WL_STATE_CRC_LEN_V2); - result = this->flash_drv->erase_range(this->addr_state1, this->state_size); + result = this->partition->erase_range(this->addr_state1, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state1, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state1, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); - result = this->flash_drv->erase_range(this->addr_state2, this->state_size); + result = this->partition->erase_range(this->addr_state2, this->state_size); WL_RESULT_CHECK(result); - result = this->flash_drv->write(this->addr_state2, &this->state, sizeof(wl_state_t)); + result = this->partition->write(this->addr_state2, &this->state, sizeof(wl_state_t)); WL_RESULT_CHECK(result); ESP_LOGD(TAG, "%s - wl_dummy_sec_move_count= 0x%08x, wl_dummy_sec_pos= 0x%08x, ", __func__, this->state.wl_dummy_sec_move_count, this->state.wl_dummy_sec_pos); } @@ -548,7 +549,7 @@ esp_err_t WL_Flash::erase_sector(size_t sector) result = this->updateWL(); WL_RESULT_CHECK(result); size_t virt_addr = this->calcAddr(sector * this->cfg.flash_sector_size); - result = this->flash_drv->erase_sector((this->cfg.wl_partition_start_addr + virt_addr) / this->cfg.flash_sector_size); + result = this->partition->erase_sector((this->cfg.wl_partition_start_addr + virt_addr) / this->cfg.flash_sector_size); WL_RESULT_CHECK(result); return result; } @@ -580,11 +581,11 @@ esp_err_t WL_Flash::write(size_t dest_addr, const void *src, size_t size) uint32_t count = (size - 1) / this->cfg.wl_page_size; for (size_t i = 0; i < count; i++) { size_t virt_addr = this->calcAddr(dest_addr + i * this->cfg.wl_page_size); - result = this->flash_drv->write(this->cfg.wl_partition_start_addr + virt_addr, &((uint8_t *)src)[i * this->cfg.wl_page_size], this->cfg.wl_page_size); + result = this->partition->write(this->cfg.wl_partition_start_addr + virt_addr, &((uint8_t *)src)[i * this->cfg.wl_page_size], this->cfg.wl_page_size); WL_RESULT_CHECK(result); } size_t virt_addr_last = this->calcAddr(dest_addr + count * this->cfg.wl_page_size); - result = this->flash_drv->write(this->cfg.wl_partition_start_addr + virt_addr_last, &((uint8_t *)src)[count * this->cfg.wl_page_size], size - count * this->cfg.wl_page_size); + result = this->partition->write(this->cfg.wl_partition_start_addr + virt_addr_last, &((uint8_t *)src)[count * this->cfg.wl_page_size], size - count * this->cfg.wl_page_size); WL_RESULT_CHECK(result); return result; } @@ -600,18 +601,18 @@ esp_err_t WL_Flash::read(size_t src_addr, void *dest, size_t size) for (size_t i = 0; i < count; i++) { size_t virt_addr = this->calcAddr(src_addr + i * this->cfg.wl_page_size); ESP_LOGV(TAG, "%s - real_addr= 0x%08x, size= 0x%08x", __func__, (uint32_t) (this->cfg.wl_partition_start_addr + virt_addr), (uint32_t) size); - result = this->flash_drv->read(this->cfg.wl_partition_start_addr + virt_addr, &((uint8_t *)dest)[i * this->cfg.wl_page_size], this->cfg.wl_page_size); + result = this->partition->read(this->cfg.wl_partition_start_addr + virt_addr, &((uint8_t *)dest)[i * this->cfg.wl_page_size], this->cfg.wl_page_size); WL_RESULT_CHECK(result); } size_t virt_addr_last = this->calcAddr(src_addr + count * this->cfg.wl_page_size); - result = this->flash_drv->read(this->cfg.wl_partition_start_addr + virt_addr_last, &((uint8_t *)dest)[count * this->cfg.wl_page_size], size - count * this->cfg.wl_page_size); + result = this->partition->read(this->cfg.wl_partition_start_addr + virt_addr_last, &((uint8_t *)dest)[count * this->cfg.wl_page_size], size - count * this->cfg.wl_page_size); WL_RESULT_CHECK(result); return result; } -Flash_Access *WL_Flash::get_drv() +Partition *WL_Flash::get_part() { - return this->flash_drv; + return this->partition; } wl_config_t *WL_Flash::get_cfg() { diff --git a/components/wear_levelling/private_include/Partition.h b/components/wear_levelling/private_include/Partition.h index be5e7c54cc60..695bfd6fa425 100644 --- a/components/wear_levelling/private_include/Partition.h +++ b/components/wear_levelling/private_include/Partition.h @@ -32,6 +32,7 @@ class Partition : public Flash_Access virtual esp_err_t read(size_t src_addr, void *dest, size_t size); virtual size_t get_sector_size(); + virtual bool is_readonly(); virtual ~Partition(); protected: diff --git a/components/wear_levelling/private_include/WL_Ext_Perf.h b/components/wear_levelling/private_include/WL_Ext_Perf.h index 2b520790b902..809e5176ece7 100644 --- a/components/wear_levelling/private_include/WL_Ext_Perf.h +++ b/components/wear_levelling/private_include/WL_Ext_Perf.h @@ -6,6 +6,7 @@ #ifndef _WL_Ext_Perf_H_ #define _WL_Ext_Perf_H_ +#include "Partition.h" #include "WL_Flash.h" #include "WL_Ext_Cfg.h" @@ -15,7 +16,7 @@ class WL_Ext_Perf : public WL_Flash WL_Ext_Perf(); ~WL_Ext_Perf() override; - esp_err_t config(WL_Config_s *cfg, Flash_Access *flash_drv) override; + esp_err_t config(WL_Config_s *cfg, Partition *partition) override; esp_err_t init() override; size_t get_flash_size() override; diff --git a/components/wear_levelling/private_include/WL_Ext_Safe.h b/components/wear_levelling/private_include/WL_Ext_Safe.h index f63f780fc390..45e1e63c37b5 100644 --- a/components/wear_levelling/private_include/WL_Ext_Safe.h +++ b/components/wear_levelling/private_include/WL_Ext_Safe.h @@ -6,6 +6,7 @@ #ifndef _WL_Ext_Safe_H_ #define _WL_Ext_Safe_H_ +#include "Partition.h" #include "WL_Flash.h" #include "WL_Ext_Cfg.h" #include "WL_Ext_Perf.h" @@ -16,7 +17,7 @@ class WL_Ext_Safe : public WL_Ext_Perf WL_Ext_Safe(); ~WL_Ext_Safe() override; - esp_err_t config(WL_Config_s *cfg, Flash_Access *flash_drv) override; + esp_err_t config(WL_Config_s *cfg, Partition *partition) override; esp_err_t init() override; size_t get_flash_size() override; diff --git a/components/wear_levelling/private_include/WL_Flash.h b/components/wear_levelling/private_include/WL_Flash.h index b7d652b4dd9d..58899ce83194 100644 --- a/components/wear_levelling/private_include/WL_Flash.h +++ b/components/wear_levelling/private_include/WL_Flash.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include "esp_err.h" #include "Flash_Access.h" +#include "Partition.h" #include "WL_Config.h" #include "WL_State.h" @@ -21,7 +22,7 @@ public : WL_Flash(); ~WL_Flash() override; - virtual esp_err_t config(wl_config_t *cfg, Flash_Access *flash_drv); + virtual esp_err_t config(wl_config_t *cfg, Partition *flash_drv); virtual esp_err_t init(); size_t get_flash_size() override; @@ -35,7 +36,7 @@ public : esp_err_t flush() override; - Flash_Access *get_drv(); + Partition *get_part(); wl_config_t *get_cfg(); protected: @@ -43,7 +44,7 @@ public : bool initialized = false; wl_state_t state; wl_config_t cfg; - Flash_Access *flash_drv = NULL; + Partition *partition = NULL; size_t addr_cfg; size_t addr_state1; diff --git a/components/wear_levelling/wear_levelling.cpp b/components/wear_levelling/wear_levelling.cpp index dd568f0c5bec..66baf6e3f7a7 100644 --- a/components/wear_levelling/wear_levelling.cpp +++ b/components/wear_levelling/wear_levelling.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -177,9 +177,9 @@ esp_err_t wl_unmount(wl_handle_t handle) // We have to flush state of the component result = s_instances[handle].instance->flush(); // We use placement new in wl_mount, so call destructor directly - Flash_Access *drv = s_instances[handle].instance->get_drv(); - drv->~Flash_Access(); - free(drv); + Partition *part = s_instances[handle].instance->get_part(); + part->~Partition(); + free(part); s_instances[handle].instance->~WL_Flash(); free(s_instances[handle].instance); s_instances[handle].instance = NULL; From ab1eb37fe8c5624ed8efb0629747aa4c5d4ae0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20M=C3=BAdry?= Date: Mon, 17 Jul 2023 11:59:28 +0200 Subject: [PATCH 2/3] feat(partition_table): Add read-only partition flag and functionality --- .../include/esp_flash_partitions.h | 12 +- components/esp_common/include/esp_err.h | 1 + components/esp_common/src/esp_err_to_name.c | 3 + .../esp_partition/include/esp_partition.h | 4 + components/esp_partition/partition.c | 3 +- components/esp_partition/partition_linux.c | 6 + components/esp_partition/partition_target.c | 31 +- components/fatfs/diskio/diskio_wl.c | 2 +- components/fatfs/vfs/vfs_fat_spiflash.c | 14 +- .../host_test/fixtures/test_fixtures.hpp | 7 +- components/nvs_flash/include/nvs.h | 2 + components/nvs_flash/include/nvs_handle.hpp | 1 + components/nvs_flash/src/nvs_partition.cpp | 23 +- components/nvs_flash/src/nvs_partition.hpp | 7 +- .../nvs_flash/src/nvs_partition_manager.cpp | 7 +- components/nvs_flash/src/partition.hpp | 23 +- .../nvs_flash/test_nvs_host/test_fixtures.hpp | 26 +- components/partition_table/gen_esp32part.py | 13 +- components/partition_table/parttool.py | 25 +- components/spi_flash/esp_flash_api.c | 27 +- components/spi_flash/include/esp_flash.h | 8 +- components/spi_flash/spi_flash_os_func_app.c | 7 +- components/spiffs/esp_spiffs.c | 30 +- components/vfs/include/esp_vfs.h | 9 +- components/vfs/vfs.c | 63 ++- components/wear_levelling/WL_Flash.cpp | 2 +- .../wear_levelling/private_include/WL_Flash.h | 2 +- components/wear_levelling/wear_levelling.cpp | 6 +- docs/en/api-guides/partition-tables.rst | 16 +- tools/ci/check_copyright_ignore.txt | 3 - tools/test_apps/.build-test-rules.yml | 17 + .../partition_table_readonly/CMakeLists.txt | 6 + .../partition_table_readonly/README.md | 2 + .../filesystem_image/dir/dirf.txt | 0 .../filesystem_image/hello.txt | 1 + .../main/CMakeLists.txt | 16 + .../partition_table_readonly/main/main.c | 359 ++++++++++++++++++ .../partition_table_readonly/nvs_data.csv | 13 + .../partitions_example.csv | 9 + .../pytest_partition_table_readonly.py | 31 ++ .../sdkconfig.ci.default | 1 + .../sdkconfig.ci.encrypted | 9 + .../sdkconfig.defaults | 20 + 43 files changed, 763 insertions(+), 104 deletions(-) create mode 100644 tools/test_apps/storage/partition_table_readonly/CMakeLists.txt create mode 100644 tools/test_apps/storage/partition_table_readonly/README.md create mode 100644 tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt create mode 100644 tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt create mode 100644 tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt create mode 100644 tools/test_apps/storage/partition_table_readonly/main/main.c create mode 100644 tools/test_apps/storage/partition_table_readonly/nvs_data.csv create mode 100644 tools/test_apps/storage/partition_table_readonly/partitions_example.csv create mode 100644 tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py create mode 100644 tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default create mode 100644 tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted create mode 100644 tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults diff --git a/components/bootloader_support/include/esp_flash_partitions.h b/components/bootloader_support/include/esp_flash_partitions.h index b1a83640ce1e..47fe97596109 100644 --- a/components/bootloader_support/include/esp_flash_partitions.h +++ b/components/bootloader_support/include/esp_flash_partitions.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,7 @@ extern "C" { #define PART_SUBTYPE_END 0xff #define PART_FLAG_ENCRYPTED (1<<0) +#define PART_FLAG_READONLY (1<<1) /* The md5sum value is found this many bytes after the ESP_PARTITION_MAGIC_MD5 offset */ #define ESP_PARTITION_MD5_OFFSET 16 @@ -92,6 +93,15 @@ typedef struct { */ esp_err_t esp_partition_table_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions); +/** + * Check whether the region on the main flash is not read-only. + * + * @param addr Start address of the region + * @param size Size of the region + * + * @return true if the region is safe to write, otherwise false. + */ +bool esp_partition_is_flash_region_writable(size_t addr, size_t size); /** * Check whether the region on the main flash is safe to write. diff --git a/components/esp_common/include/esp_err.h b/components/esp_common/include/esp_err.h index ad2611f36bce..30789475a5be 100644 --- a/components/esp_common/include/esp_err.h +++ b/components/esp_common/include/esp_err.h @@ -34,6 +34,7 @@ typedef int esp_err_t; #define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */ #define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */ #define ESP_ERR_NOT_FINISHED 0x10C /*!< Operation has not fully completed */ +#define ESP_ERR_NOT_ALLOWED 0x10D /*!< Operation is not allowed */ #define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */ diff --git a/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index 6e17f8519f5f..7aca0e17e4d2 100644 --- a/components/esp_common/src/esp_err_to_name.c +++ b/components/esp_common/src/esp_err_to_name.c @@ -133,6 +133,9 @@ static const esp_err_msg_t esp_err_msg_table[] = { # endif # ifdef ESP_ERR_NOT_FINISHED ERR_TBL_IT(ESP_ERR_NOT_FINISHED), /* 268 0x10c Operation has not fully completed */ +# endif +# ifdef ESP_ERR_NOT_ALLOWED + ERR_TBL_IT(ESP_ERR_NOT_ALLOWED), /* 269 0x10d Operation is not allowed */ # endif // components/nvs_flash/include/nvs.h # ifdef ESP_ERR_NVS_BASE diff --git a/components/esp_partition/include/esp_partition.h b/components/esp_partition/include/esp_partition.h index b9950c96efea..b4f8c2d2e5ab 100644 --- a/components/esp_partition/include/esp_partition.h +++ b/components/esp_partition/include/esp_partition.h @@ -132,6 +132,7 @@ typedef struct { uint32_t erase_size; /*!< size the erase operation should be aligned to */ char label[17]; /*!< partition label, zero-terminated ASCII string */ bool encrypted; /*!< flag is set to true if partition is encrypted */ + bool readonly; /*!< flag is set to true if partition is read-only */ } esp_partition_t; /** @@ -270,6 +271,7 @@ esp_err_t esp_partition_read(const esp_partition_t* partition, * @return ESP_OK, if data was written successfully; * ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size; * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; + * ESP_ERR_NOT_ALLOWED, if partition is read-only; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_write(const esp_partition_t* partition, @@ -322,6 +324,7 @@ esp_err_t esp_partition_read_raw(const esp_partition_t* partition, * @return ESP_OK, if data was written successfully; * ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size; * ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition; + * ESP_ERR_NOT_ALLOWED, if partition is read-only; * or one of the error codes from lower-level flash driver. */ esp_err_t esp_partition_write_raw(const esp_partition_t* partition, @@ -341,6 +344,7 @@ esp_err_t esp_partition_write_raw(const esp_partition_t* partition, * @return ESP_OK, if the range was erased successfully; * ESP_ERR_INVALID_ARG, if iterator or dst are NULL; * ESP_ERR_INVALID_SIZE, if erase would go out of bounds of the partition; + * ESP_ERR_NOT_ALLOWED, if partition is read-only; * or one of error codes from lower-level flash driver. */ esp_err_t esp_partition_erase_range(const esp_partition_t* partition, diff --git a/components/esp_partition/partition.c b/components/esp_partition/partition.c index 640718a6b1b6..ae4ca4b7ba1f 100644 --- a/components/esp_partition/partition.c +++ b/components/esp_partition/partition.c @@ -154,6 +154,7 @@ static esp_err_t load_partitions(void) item->info.type = entry.type; item->info.subtype = entry.subtype; item->info.encrypted = entry.flags & PART_FLAG_ENCRYPTED; + item->info.readonly = entry.flags & PART_FLAG_READONLY; item->user_registered = false; #if CONFIG_IDF_TARGET_LINUX @@ -349,7 +350,6 @@ const esp_partition_t *esp_partition_find_first(esp_partition_type_t type, return res; } - void esp_partition_iterator_release(esp_partition_iterator_t iterator) { // iterator == NULL is okay @@ -384,6 +384,7 @@ const esp_partition_t *esp_partition_verify(const esp_partition_t *partition) esp_partition_iterator_release(it); return NULL; } + esp_err_t esp_partition_register_external(esp_flash_t *flash_chip, size_t offset, size_t size, const char *label, esp_partition_type_t type, esp_partition_subtype_t subtype, const esp_partition_t **out_partition) diff --git a/components/esp_partition/partition_linux.c b/components/esp_partition/partition_linux.c index 6af40c506711..cf3e86491e7f 100644 --- a/components/esp_partition/partition_linux.c +++ b/components/esp_partition/partition_linux.c @@ -369,6 +369,9 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offse { assert(partition != NULL && s_spiflash_mem_file_buf != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (partition->encrypted) { return ESP_ERR_NOT_SUPPORTED; } @@ -450,6 +453,9 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t off { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (offset > partition->size || offset % partition->erase_size != 0) { return ESP_ERR_INVALID_ARG; } diff --git a/components/esp_partition/partition_target.c b/components/esp_partition/partition_target.c index 25456e919c53..26a86bcc7fce 100644 --- a/components/esp_partition/partition_target.c +++ b/components/esp_partition/partition_target.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -64,6 +64,9 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size) { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (dst_offset > partition->size) { return ESP_ERR_INVALID_ARG; } @@ -103,6 +106,9 @@ esp_err_t esp_partition_write_raw(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size) { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (dst_offset > partition->size) { return ESP_ERR_INVALID_ARG; } @@ -118,6 +124,9 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t offset, size_t size) { assert(partition != NULL); + if (partition->readonly) { + return ESP_ERR_NOT_ALLOWED; + } if (offset > partition->size) { return ESP_ERR_INVALID_ARG; } @@ -193,9 +202,25 @@ bool esp_partition_check_identity(const esp_partition_t *partition_1, const esp_ return false; } +bool esp_partition_is_flash_region_writable(size_t addr, size_t size) +{ + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); + for (; it != NULL; it = esp_partition_next(it)) { + const esp_partition_t *p = esp_partition_get(it); + if (p->readonly) { + if (addr >= p->address && addr < p->address + p->size) { + return false; + } + if (addr < p->address && addr + size > p->address) { + return false; + } + } + } + return true; +} + bool esp_partition_main_flash_region_safe(size_t addr, size_t size) { - bool result = true; if (addr <= ESP_PARTITION_TABLE_OFFSET + ESP_PARTITION_TABLE_MAX_LEN) { return false; } @@ -206,5 +231,5 @@ bool esp_partition_main_flash_region_safe(size_t addr, size_t size) if (addr < p->address && addr + size > p->address) { return false; } - return result; + return true; } diff --git a/components/fatfs/diskio/diskio_wl.c b/components/fatfs/diskio/diskio_wl.c index 849c6b98a783..8e20290e8c96 100644 --- a/components/fatfs/diskio/diskio_wl.c +++ b/components/fatfs/diskio/diskio_wl.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/fatfs/vfs/vfs_fat_spiflash.c b/components/fatfs/vfs/vfs_fat_spiflash.c index d227de821bee..0db2f70e0a2d 100644 --- a/components/fatfs/vfs/vfs_fat_spiflash.c +++ b/components/fatfs/vfs/vfs_fat_spiflash.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,7 +8,6 @@ #include #include "esp_check.h" #include "esp_log.h" -#include "esp_vfs.h" #include "esp_vfs_fat.h" #include "vfs_fat_internal.h" #include "diskio_impl.h" @@ -29,6 +28,8 @@ typedef struct vfs_fat_spiflash_ctx_t { static vfs_fat_spiflash_ctx_t *s_ctx[FF_VOLUMES] = {}; +extern esp_err_t esp_vfs_set_readonly_flag(const char* base_path); // from vfs/vfs.c to set readonly flag in esp_vfs_t struct externally + static bool s_get_context_id_by_label(const char *label, uint32_t *out_id) { vfs_fat_spiflash_ctx_t *p_ctx = NULL; @@ -160,6 +161,10 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path, assert(ctx_id != FF_VOLUMES); s_ctx[ctx_id] = ctx; + if (data_partition->readonly) { + esp_vfs_set_readonly_flag(base_path); + } + return ESP_OK; fail: @@ -296,6 +301,11 @@ esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path, ret = ESP_FAIL; goto fail; } + + if (data_partition->readonly) { + esp_vfs_set_readonly_flag(base_path); + } + return ESP_OK; fail: diff --git a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp index fdf2c110e340..03172d7db02d 100644 --- a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp +++ b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -76,6 +76,11 @@ class PartitionMock : public nvs::Partition { return size; } + bool get_readonly() override + { + return partition.readonly; + } + const esp_partition_t partition; private: diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index 49fe9b7d60de..2384680fee37 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -135,6 +135,7 @@ typedef struct nvs_opaque_iterator_t *nvs_iterator_t; * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is no space for a new entry or there are too many different * namespaces (maximum allowed different namespaces: 254) + * - ESP_ERR_NOT_ALLOWED if the NVS partition is read-only and mode is NVS_READWRITE * - other error codes from the underlying storage driver */ esp_err_t nvs_open(const char* namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle); @@ -166,6 +167,7 @@ esp_err_t nvs_open(const char* namespace_name, nvs_open_mode_t open_mode, nvs_ha * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is no space for a new entry or there are too many different * namespaces (maximum allowed different namespaces: 254) + * - ESP_ERR_NOT_ALLOWED if the NVS partition is read-only and mode is NVS_READWRITE * - other error codes from the underlying storage driver */ esp_err_t nvs_open_from_partition(const char *part_name, const char* namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle); diff --git a/components/nvs_flash/include/nvs_handle.hpp b/components/nvs_flash/include/nvs_handle.hpp index b09d013d22ab..2421e1a86176 100644 --- a/components/nvs_flash/include/nvs_handle.hpp +++ b/components/nvs_flash/include/nvs_handle.hpp @@ -222,6 +222,7 @@ class NVSHandle { * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and * mode is NVS_READONLY * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints + * - ESP_ERR_NOT_ALLOWED if the NVS partition is read-only and mode is NVS_READWRITE * - other error codes from the underlying storage driver * * @return unique pointer of an nvs handle on success, an empty unique pointer otherwise diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp index f1b7d36d2dca..ec15d58baada 100644 --- a/components/nvs_flash/src/nvs_partition.cpp +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -1,16 +1,8 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "nvs_partition.hpp" @@ -74,4 +66,9 @@ uint32_t NVSPartition::get_size() return mESPPartition->size; } +bool NVSPartition::get_readonly() +{ + return mESPPartition->readonly; +} + } // nvs diff --git a/components/nvs_flash/src/nvs_partition.hpp b/components/nvs_flash/src/nvs_partition.hpp index 8ed544a6b112..e8f64afc54cc 100644 --- a/components/nvs_flash/src/nvs_partition.hpp +++ b/components/nvs_flash/src/nvs_partition.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -99,6 +99,11 @@ class NVSPartition : public Partition, public intrusive_list_node, */ uint32_t get_size() override; + /** + * @return true if the partition is read-only. + */ + bool get_readonly() override; + protected: const esp_partition_t* mESPPartition; }; diff --git a/components/nvs_flash/src/nvs_partition_manager.cpp b/components/nvs_flash/src/nvs_partition_manager.cpp index f43ca130a19a..eecde60ea592 100644 --- a/components/nvs_flash/src/nvs_partition_manager.cpp +++ b/components/nvs_flash/src/nvs_partition_manager.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -192,6 +192,11 @@ esp_err_t NVSPartitionManager::open_handle(const char *part_name, return ESP_ERR_NVS_PART_NOT_FOUND; } + if (open_mode == NVS_READWRITE && const_cast(sHandle->getPart())->get_readonly()) { + return ESP_ERR_NOT_ALLOWED; + } + + esp_err_t err = sHandle->createOrOpenNamespace(ns_name, open_mode == NVS_READWRITE, nsIndex); if (err != ESP_OK) { return err; diff --git a/components/nvs_flash/src/partition.hpp b/components/nvs_flash/src/partition.hpp index 4cbfb6d9ddf4..e649f84b200f 100644 --- a/components/nvs_flash/src/partition.hpp +++ b/components/nvs_flash/src/partition.hpp @@ -1,16 +1,8 @@ -// Copyright 2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #ifndef PARTITION_HPP_ #define PARTITION_HPP_ @@ -52,6 +44,11 @@ class Partition { * Return the partition size in bytes. */ virtual uint32_t get_size() = 0; + + /** + * Return true if the partition is read-only. + */ + virtual bool get_readonly() = 0; }; } // nvs diff --git a/components/nvs_flash/test_nvs_host/test_fixtures.hpp b/components/nvs_flash/test_nvs_host/test_fixtures.hpp index 473f1da973e5..4a50a1daadfc 100644 --- a/components/nvs_flash/test_nvs_host/test_fixtures.hpp +++ b/components/nvs_flash/test_nvs_host/test_fixtures.hpp @@ -1,16 +1,8 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include "nvs_partition.hpp" #include "nvs_encrypted_partition.hpp" #include "spi_flash_emulation.h" @@ -27,6 +19,7 @@ class PartitionEmulation : public nvs::Partition { assert(partition_name); assert(flash_emu); assert(size); + readonly = false; } const char *get_partition_name() override @@ -101,6 +94,11 @@ class PartitionEmulation : public nvs::Partition { return size; } + bool get_readonly() override + { + return readonly; + } + private: const char *partition_name; @@ -109,6 +107,8 @@ class PartitionEmulation : public nvs::Partition { uint32_t address; uint32_t size; + + bool readonly; }; struct PartitionEmulationFixture { diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index 5eb8475539ea..5e18a86ed078 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -7,7 +7,7 @@ # See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html # for explanation of partition table structure and uses. # -# SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from __future__ import division, print_function, unicode_literals @@ -32,7 +32,7 @@ SECURE_V1 = 'v1' SECURE_V2 = 'v2' -__version__ = '1.2' +__version__ = '1.3' APP_TYPE = 0x00 DATA_TYPE = 0x01 @@ -341,7 +341,8 @@ class PartitionDefinition(object): # dictionary maps flag name (as used in CSV flags list, property name) # to bit set in flags words in binary format FLAGS = { - 'encrypted': 0 + 'encrypted': 0, + 'readonly': 1 } # add subtypes for the 16 OTA slot values ("ota_XX, etc.") @@ -355,6 +356,7 @@ def __init__(self): self.offset = None self.size = None self.encrypted = False + self.readonly = False @classmethod def from_csv(cls, line, line_no): @@ -454,6 +456,11 @@ def verify(self): critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has " 'non-matching type 0x%x and subtype 0x%x. Mistake in partition table?' % (self.name, self.type, self.subtype)) + always_rw_data_subtypes = [SUBTYPES[DATA_TYPE]['ota'], SUBTYPES[DATA_TYPE]['coredump']] + if self.type == TYPES['data'] and self.subtype in always_rw_data_subtypes and self.readonly is True: + raise ValidationError(self, "'%s' partition of type %s and subtype %s is always read-write and cannot be read-only" % + (self.name, self.type, self.subtype)) + STRUCT_FORMAT = b'<2sBBLL16sL' @classmethod diff --git a/components/partition_table/parttool.py b/components/partition_table/parttool.py index 3c30d850cc5c..2b6920cb5988 100755 --- a/components/partition_table/parttool.py +++ b/components/partition_table/parttool.py @@ -3,7 +3,7 @@ # parttool is used to perform partition level operations - reading, # writing, erasing and getting info about the partition. # -# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from __future__ import division, print_function @@ -16,7 +16,7 @@ import gen_esp32part as gen -__version__ = '2.0' +__version__ = '2.1' COMPONENTS_PATH = os.path.expandvars(os.path.join('$IDF_PATH', 'components')) ESPTOOL_PY = os.path.join(COMPONENTS_PATH, 'esptool_py', 'esptool', 'esptool.py') @@ -159,10 +159,13 @@ def read_partition(self, partition_id, output): self._call_esptool(['read_flash', str(partition.offset), str(partition.size), output] + self.esptool_read_args) def write_partition(self, partition_id, input): - self.erase_partition(partition_id) - partition = self.get_partition_info(partition_id) + if partition.readonly: + raise Exception(f'"{partition.name}" partition is read-only') + + self.erase_partition(partition_id) + with open(input, 'rb') as input_file: content_len = len(input_file.read()) @@ -209,7 +212,8 @@ def _get_partition_info(target, partition_id, info): 'subtype': '{}'.format(p.subtype), 'offset': '0x{:x}'.format(p.offset), 'size': '0x{:x}'.format(p.size), - 'encrypted': '{}'.format(p.encrypted) + 'encrypted': '{}'.format(p.encrypted), + 'readonly': '{}'.format(p.readonly) } for i in info: infos += [info_dict[i]] @@ -269,7 +273,8 @@ def main(): print_partition_info_subparser = subparsers.add_parser('get_partition_info', help='get partition information', parents=[partition_selection_parser]) print_partition_info_subparser.add_argument('--info', help='type of partition information to get', - choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted'], default=['offset', 'size'], nargs='+') + choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted', 'readonly'], + default=['offset', 'size'], nargs='+') print_partition_info_subparser.add_argument('--part_list', help='Get a list of partitions suitable for a given type', action='store_true') args = parser.parse_args() @@ -329,10 +334,10 @@ def main(): # Create the operation table and execute the operation common_args = {'target':target, 'partition_id':partition_id} parttool_ops = { - 'erase_partition':(_erase_partition, []), - 'read_partition':(_read_partition, ['output']), - 'write_partition':(_write_partition, ['input']), - 'get_partition_info':(_get_partition_info, ['info']) + 'erase_partition': (_erase_partition, []), + 'read_partition': (_read_partition, ['output']), + 'write_partition': (_write_partition, ['input']), + 'get_partition_info': (_get_partition_info, ['info']) } (op, op_args) = parttool_ops[args.operation] diff --git a/components/spi_flash/esp_flash_api.c b/components/spi_flash/esp_flash_api.c index a3a932cf0e39..f639a37fcb0c 100644 --- a/components/spi_flash/esp_flash_api.c +++ b/components/spi_flash/esp_flash_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -47,25 +47,26 @@ DRAM_ATTR static const char TAG[] = "spi_flash"; /* CHECK_WRITE_ADDRESS macro to fail writes which land in the bootloader, partition table, or running application region. */ -#if CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED -#define CHECK_WRITE_ADDRESS(CHIP, ADDR, SIZE) -#else /* FAILS or ABORTS */ -#define CHECK_WRITE_ADDRESS(CHIP, ADDR, SIZE) do { \ - if (CHIP && CHIP->os_func->region_protected && CHIP->os_func->region_protected(CHIP->os_func_data, ADDR, SIZE)) { \ - UNSAFE_WRITE_ADDRESS; \ - } \ +#define CHECK_WRITE_ADDRESS(CHIP, ADDR, SIZE) do { \ + if (CHIP && CHIP->os_func->region_protected) { \ + esp_err_t ret = CHIP->os_func->region_protected(CHIP->os_func_data, ADDR, SIZE); \ + if (ret == ESP_ERR_NOT_ALLOWED) { \ + return ret; /* ESP_ERR_NOT_ALLOWED from read-only partition check */ \ + } else if (ret != ESP_OK) { \ + UNSAFE_WRITE_ADDRESS; /* FAILS or ABORTS */ \ + } \ + } \ } while(0) -#endif // CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED /* Convenience macro for beginning of all API functions. * Check the return value of `rom_spiflash_api_funcs->chip_check` is correct, * and the chip supports the operation in question. */ -#define VERIFY_CHIP_OP(op) do { \ +#define VERIFY_CHIP_OP(op) do { \ if (err != ESP_OK) return err; \ - if (chip->chip_drv->op == NULL) { \ - return ESP_ERR_FLASH_UNSUPPORTED_CHIP; \ - } \ + if (chip->chip_drv->op == NULL) { \ + return ESP_ERR_FLASH_UNSUPPORTED_CHIP; \ + } \ } while (0) diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index e5b92b941256..9eedaa753fc6 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -191,6 +191,7 @@ esp_err_t esp_flash_read_unique_chip_id(esp_flash_t *chip, uint64_t *out_id); * @return * - ESP_OK on success, * - ESP_ERR_NOT_SUPPORTED if the chip is not able to perform the operation. This is indicated by WREN = 1 after the command is sent. + * - ESP_ERR_NOT_ALLOWED if a read-only partition is present. * - Other flash error code if operation failed. */ esp_err_t esp_flash_erase_chip(esp_flash_t *chip); @@ -211,6 +212,7 @@ esp_err_t esp_flash_erase_chip(esp_flash_t *chip); * @return * - ESP_OK on success, * - ESP_ERR_NOT_SUPPORTED if the chip is not able to perform the operation. This is indicated by WREN = 1 after the command is sent. + * - ESP_ERR_NOT_ALLOWED if the address range (start -- start + len) overlaps with a read-only partition address space * - Other flash error code if operation failed. */ esp_err_t esp_flash_erase_region(esp_flash_t *chip, uint32_t start, uint32_t len); @@ -316,9 +318,10 @@ esp_err_t esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint * There are no alignment constraints on buffer, address or length. * * @return - * - ESP_OK on success, + * - ESP_OK on success * - ESP_FAIL, bad write, this will be detected only when CONFIG_SPI_FLASH_VERIFY_WRITE is enabled * - ESP_ERR_NOT_SUPPORTED if the chip is not able to perform the operation. This is indicated by WREN = 1 after the command is sent. + * - ESP_ERR_NOT_ALLOWED if the address range (address -- address + length) overlaps with a read-only partition address space * - Other flash error code if operation failed. */ esp_err_t esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t address, uint32_t length); @@ -337,6 +340,7 @@ esp_err_t esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t addres * - ESP_FAIL: bad write, this will be detected only when CONFIG_SPI_FLASH_VERIFY_WRITE is enabled * - ESP_ERR_NOT_SUPPORTED: encrypted write not supported for this chip. * - ESP_ERR_INVALID_ARG: Either the address, buffer or length is invalid. + * - ESP_ERR_NOT_ALLOWED if the address range (address -- address + length) overlaps with a read-only partition address space */ esp_err_t esp_flash_write_encrypted(esp_flash_t *chip, uint32_t address, const void *buffer, uint32_t length); diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index f242bb46b3fa..31aa11b8a98e 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -206,15 +206,20 @@ static IRAM_ATTR void release_buffer_malloc(void* arg, void *temp_buf) static IRAM_ATTR esp_err_t main_flash_region_protected(void* arg, size_t start_addr, size_t size) { + if (!esp_partition_is_flash_region_writable(start_addr, size)) { + return ESP_ERR_NOT_ALLOWED; + } +#if !CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED if (((app_func_arg_t*)arg)->no_protect || esp_partition_main_flash_region_safe(start_addr, size)) { //ESP_OK = 0, also means protected==0 return ESP_OK; } else { return ESP_ERR_NOT_SUPPORTED; } +#endif // !CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED + return ESP_OK; } - static IRAM_ATTR void main_flash_op_status(uint32_t op_status) { bool is_erasing = op_status & SPI_FLASH_OS_IS_ERASING_STATUS_FLAG; diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c index c6d4a5b1edf7..5731b322b7aa 100644 --- a/components/spiffs/esp_spiffs.c +++ b/components/spiffs/esp_spiffs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -402,8 +402,24 @@ esp_err_t esp_spiffs_gc(const char* partition_label, size_t size_to_gc) esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf) { assert(conf->base_path); + + esp_err_t err = esp_spiffs_init(conf); + if (err != ESP_OK) { + return err; + } + + int index; + if (esp_spiffs_by_label(conf->partition_label, &index) != ESP_OK) { + return ESP_ERR_INVALID_STATE; + } + + int vfs_flags = ESP_VFS_FLAG_CONTEXT_PTR; + if (_efs[index]->partition->readonly) { + vfs_flags |= ESP_VFS_FLAG_READONLY_FS; + } + const esp_vfs_t vfs = { - .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .flags = vfs_flags, .write_p = &vfs_spiffs_write, .lseek_p = &vfs_spiffs_lseek, .read_p = &vfs_spiffs_read, @@ -433,16 +449,6 @@ esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf) #endif // CONFIG_VFS_SUPPORT_DIR }; - esp_err_t err = esp_spiffs_init(conf); - if (err != ESP_OK) { - return err; - } - - int index; - if (esp_spiffs_by_label(conf->partition_label, &index) != ESP_OK) { - return ESP_ERR_INVALID_STATE; - } - strlcat(_efs[index]->base_path, conf->base_path, ESP_VFS_PATH_MAX + 1); err = esp_vfs_register(conf->base_path, &vfs, _efs[index]); if (err != ESP_OK) { diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index f9e5e785bed9..ac0029e3f1de 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,6 +53,11 @@ extern "C" { */ #define ESP_VFS_FLAG_CONTEXT_PTR 1 +/** + * Flag which indicates that FS is located on read-only partition. + */ +#define ESP_VFS_FLAG_READONLY_FS 2 + /* * @brief VFS identificator used for esp_vfs_register_with_id() */ @@ -91,7 +96,7 @@ typedef struct */ typedef struct { - int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */ + int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR and/or ESP_VFS_FLAG_READONLY_FS or ESP_VFS_FLAG_DEFAULT */ union { ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); /*!< Write with context pointer */ ssize_t (*write)(int fd, const void * data, size_t size); /*!< Write without context pointer */ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 6adf9db1394f..d68699af05c5 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -270,6 +270,29 @@ esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd) return ret; } +/* + * Set ESP_VFS_FLAG_READONLY_FS read-only flag for a registered virtual filesystem + * for given path prefix. Should be only called from the esp_vfs_*filesystem* register + * or helper mount functions where vfs_t is not available to set the read-only + * flag directly (e.g. esp_vfs_fat_spiflash_mount_rw_wl). + */ +esp_err_t esp_vfs_set_readonly_flag(const char* base_path) +{ + const size_t base_path_len = strlen(base_path); + for (size_t i = 0; i < s_vfs_count; ++i) { + vfs_entry_t* vfs = s_vfs[i]; + if (vfs == NULL) { + continue; + } + if (base_path_len == vfs->path_prefix_len && + memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) { + vfs->vfs.flags |= ESP_VFS_FLAG_READONLY_FS; + return ESP_OK; + } + } + return ESP_ERR_INVALID_STATE; +} + const vfs_entry_t *get_vfs_for_index(int index) { if (index < 0 || index >= s_vfs_count) { @@ -401,6 +424,12 @@ const vfs_entry_t* get_vfs_for_path(const char* path) ret = (*pvfs->vfs.func)(__VA_ARGS__);\ } +#define CHECK_VFS_READONLY_FLAG(flags) \ + if (flags & ESP_VFS_FLAG_READONLY_FS) { \ + __errno_r(r) = EROFS; \ + return -1; \ + } + int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) { const vfs_entry_t *vfs = get_vfs_for_path(path); @@ -408,6 +437,14 @@ int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) __errno_r(r) = ENOENT; return -1; } + + int acc_mode = flags & O_ACCMODE; + int ro_filesystem = vfs->vfs.flags & ESP_VFS_FLAG_READONLY_FS; + if (acc_mode != O_RDONLY && ro_filesystem) { + __errno_r(r) = EROFS; + return -1; + } + const char *path_within_vfs = translate_path(vfs, path); int fd_within_vfs; CHECK_AND_CALL(fd_within_vfs, r, vfs, open, path_within_vfs, flags, mode); @@ -621,6 +658,9 @@ int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) __errno_r(r) = EXDEV; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs2->vfs.flags); + const char* path1_within_vfs = translate_path(vfs, n1); const char* path2_within_vfs = translate_path(vfs, n2); int ret; @@ -635,6 +675,9 @@ int esp_vfs_unlink(struct _reent *r, const char *path) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, path); int ret; CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); @@ -648,11 +691,17 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); if (vfs != vfs_dst) { __errno_r(r) = EXDEV; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs_dst->vfs.flags); + const char* src_within_vfs = translate_path(vfs, src); const char* dst_within_vfs = translate_path(vfs, dst); int ret; @@ -753,6 +802,9 @@ int esp_vfs_mkdir(const char* name, mode_t mode) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, name); int ret; CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode); @@ -767,6 +819,9 @@ int esp_vfs_rmdir(const char* name) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, name); int ret; CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs); @@ -796,6 +851,9 @@ int esp_vfs_truncate(const char *path, off_t length) __errno_r(r) = ENOENT; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + const char* path_within_vfs = translate_path(vfs, path); CHECK_AND_CALL(ret, r, vfs, truncate, path_within_vfs, length); return ret; @@ -810,6 +868,9 @@ int esp_vfs_ftruncate(int fd, off_t length) __errno_r(r) = EBADF; return -1; } + + CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + int ret; CHECK_AND_CALL(ret, r, vfs, ftruncate, local_fd, length); return ret; diff --git a/components/wear_levelling/WL_Flash.cpp b/components/wear_levelling/WL_Flash.cpp index d82b250b9f0b..b3fd31e4ff0e 100644 --- a/components/wear_levelling/WL_Flash.cpp +++ b/components/wear_levelling/WL_Flash.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/wear_levelling/private_include/WL_Flash.h b/components/wear_levelling/private_include/WL_Flash.h index 58899ce83194..5f24467e74d6 100644 --- a/components/wear_levelling/private_include/WL_Flash.h +++ b/components/wear_levelling/private_include/WL_Flash.h @@ -22,7 +22,7 @@ public : WL_Flash(); ~WL_Flash() override; - virtual esp_err_t config(wl_config_t *cfg, Partition *flash_drv); + virtual esp_err_t config(wl_config_t *cfg, Partition *partition); virtual esp_err_t init(); size_t get_flash_size() override; diff --git a/components/wear_levelling/wear_levelling.cpp b/components/wear_levelling/wear_levelling.cpp index 66baf6e3f7a7..cdbdc923c09d 100644 --- a/components/wear_levelling/wear_levelling.cpp +++ b/components/wear_levelling/wear_levelling.cpp @@ -174,10 +174,12 @@ esp_err_t wl_unmount(wl_handle_t handle) _lock_acquire(&s_instances_lock); result = check_handle(handle, __func__); if (result == ESP_OK) { - // We have to flush state of the component - result = s_instances[handle].instance->flush(); // We use placement new in wl_mount, so call destructor directly Partition *part = s_instances[handle].instance->get_part(); + // We have to flush state of the component + if (!part->is_readonly()) { + result = s_instances[handle].instance->flush(); + } part->~Partition(); free(part); s_instances[handle].instance->~WL_Flash(); diff --git a/docs/en/api-guides/partition-tables.rst b/docs/en/api-guides/partition-tables.rst index 361f70d651f2..ea77d5183fe1 100644 --- a/docs/en/api-guides/partition-tables.rst +++ b/docs/en/api-guides/partition-tables.rst @@ -166,11 +166,21 @@ If you want the partitions in the partition table to work relative to any placem Flags ~~~~~ -Only one flag is currently supported, ``encrypted``. If this field is set to ``encrypted``, this partition will be encrypted if :doc:`/security/flash-encryption` is enabled. +Two flags are currently supported, ``encrypted`` and ``readonly``: + + - If ``encrypted`` flag is set, the partition will be encrypted if :doc:`/security/flash-encryption` is enabled. -.. note:: + .. note:: + + ``app`` type partitions will always be encrypted, regardless of whether this flag is set or not. + + - If ``readonly`` flag is set, the partition will be read-only. This flag is only supported for ``data`` type partitions except ``ota``` and ``coredump``` subtypes. This flag can help to protect against the accidental writes to partition that contains critical device specific configuration data, e.g., factory data partition. + + .. note:: + + Using C file I/O API to open a file (``fopen```) in any write mode (``w``, ``w+``, ``a``, ``a+``, ``r+``) will fail and return ``NULL``. Using ``open`` with any other flag than ``O_RDONLY`` will fail and return ``-1`` while ``errno`` global variable will be set to ``EACCES``. This is also true for any other POSIX syscall function performing write or erase operations. Opening a handle in read-write mode for NVS on a read-only partition will fail and return :c:macro:`ESP_ERR_NOT_ALLOWED` error code. Using a lower level API like ``esp_partition``, ``spi_flash``, ``wear_levelling``, etc. to write to a read-only partition will result in :c:macro:`ESP_ERR_NOT_ALLOWED` error code. - ``app`` type partitions will always be encrypted, regardless of whether this flag is set or not. +You can specify multiple flags by separating them with a colon. For example, ``encrypted:readonly``. Generating Binary Partition Table --------------------------------- diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index ae1ce2ce7f2c..e97764baa603 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -591,16 +591,13 @@ components/nvs_flash/src/nvs_handle_locked.cpp components/nvs_flash/src/nvs_handle_locked.hpp components/nvs_flash/src/nvs_item_hash_list.cpp components/nvs_flash/src/nvs_pagemanager.hpp -components/nvs_flash/src/nvs_partition.cpp components/nvs_flash/src/nvs_partition_lookup.cpp components/nvs_flash/src/nvs_partition_lookup.hpp components/nvs_flash/src/nvs_platform.hpp components/nvs_flash/src/nvs_test_api.h components/nvs_flash/src/nvs_types.cpp -components/nvs_flash/src/partition.hpp components/nvs_flash/test_nvs_host/main.cpp components/nvs_flash/test_nvs_host/sdkconfig.h -components/nvs_flash/test_nvs_host/test_fixtures.hpp components/nvs_flash/test_nvs_host/test_intrusive_list.cpp components/protocomm/include/transports/protocomm_console.h components/protocomm/include/transports/protocomm_httpd.h diff --git a/tools/test_apps/.build-test-rules.yml b/tools/test_apps/.build-test-rules.yml index fa7ac86587a1..f90c78a33288 100644 --- a/tools/test_apps/.build-test-rules.yml +++ b/tools/test_apps/.build-test-rules.yml @@ -74,6 +74,23 @@ tools/test_apps/security/signed_app_no_secure_boot: temporary: true reason: No need to test on all targets +tools/test_apps/storage/partition_table_readonly: + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] + reason: these chips should be sufficient for test coverage (Xtensa and RISC-V, single and dual core) + disable: + - if: CONFIG_NAME == "encrypted" + temporary: true + reason: there are potential bugs with pytest when using flash encryption and NVS partition with nvs_create_partition_image #TODO: IDF-8300 + depends_components: + - partition_table + - spi_flash + - esp_partition + - nvs_flash + - vfs + - fatfs + - spiffs + tools/test_apps/system/bootloader_sections: disable: - if: IDF_TARGET == "esp32c2" diff --git a/tools/test_apps/storage/partition_table_readonly/CMakeLists.txt b/tools/test_apps/storage/partition_table_readonly/CMakeLists.txt new file mode 100644 index 000000000000..df4e85de8c9d --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_partition_table_readonly) diff --git a/tools/test_apps/storage/partition_table_readonly/README.md b/tools/test_apps/storage/partition_table_readonly/README.md new file mode 100644 index 000000000000..bf47d80ec649 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | diff --git a/tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt b/tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt b/tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt new file mode 100644 index 000000000000..f16e016fad27 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt @@ -0,0 +1 @@ +This is a file cointained in the generated filesystem image on the host and flashed to the ESP device diff --git a/tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt b/tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt new file mode 100644 index 000000000000..617d4cc7c833 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt @@ -0,0 +1,16 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") + +set(nvs_partition_name nvs_ro) +set(nvs_data_csv ../nvs_data.csv) +nvs_create_partition_image(${nvs_partition_name} ${nvs_data_csv} FLASH_IN_PROJECT) + +set(image ../filesystem_image) + +set(fatfs_wl_partition_name fatfs_ro) +set(fatfs_raw_partition_name fatfs_raw_ro) +fatfs_create_spiflash_image(${fatfs_wl_partition_name} ${image} FLASH_IN_PROJECT) +fatfs_create_rawflash_image(${fatfs_raw_partition_name} ${image} FLASH_IN_PROJECT) + +set(spiffs_partition_name spiffs_ro) +spiffs_create_partition_image(${spiffs_partition_name} ${image} FLASH_IN_PROJECT) diff --git a/tools/test_apps/storage/partition_table_readonly/main/main.c b/tools/test_apps/storage/partition_table_readonly/main/main.c new file mode 100644 index 000000000000..39126c8db5f4 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/main/main.c @@ -0,0 +1,359 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "esp_flash.h" +#include +#include "nvs_flash.h" +#include "nvs.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "esp_spiffs.h" +#include "esp_heap_caps.h" +#include "esp_flash_encrypt.h" +#include "esp_efuse_table.h" + +static const char* TAG = "test_readonly_partition_feature"; + +#define NUM_OF_READONLY_PARTITIONS 4 +const esp_partition_t* readonly_partitions[NUM_OF_READONLY_PARTITIONS]; + +// Partition names +const char *nvs_partition_name = "nvs_ro"; +const char *fatfs_wl_partition_name = "fatfs_ro"; +const char *fatfs_raw_partition_name = "fatfs_raw_ro"; +const char *spiffs_partition_name = "spiffs_ro"; + +// Mount paths for partitions +#define FATFS_WL_BASE_PATH "/fatfs_wl" +#define FATFS_RAW_BASE_PATH "/fatfs_raw" +#define SPIFFS_BASE_PATH "/spiffs" + +// Handle of the wear levelling library instance +static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; + +// Data in each filesystem partition +const char* cmp_string = "This is a file cointained in the generated filesystem image on the host and flashed to the ESP device"; +#define CMP_STRING_LEN 102 // 101 + '\0' + +static void fill_array_of_readonly_data_partitions(void) +{ + // This finds read-only partitions defined in the partition table + const esp_partition_t* part_nvs = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, nvs_partition_name); + const esp_partition_t* part_fatfs_wl = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, fatfs_wl_partition_name); + const esp_partition_t* part_fatfs_raw = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, fatfs_raw_partition_name); + const esp_partition_t* part_spiffs = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, spiffs_partition_name); + TEST_ASSERT_NOT_NULL(part_nvs); // NULL means partition table set wrong + TEST_ASSERT_NOT_NULL(part_fatfs_wl); + TEST_ASSERT_NOT_NULL(part_fatfs_raw); + TEST_ASSERT_NOT_NULL(part_spiffs); + + readonly_partitions[0] = part_nvs; + readonly_partitions[1] = part_fatfs_wl; + readonly_partitions[2] = part_fatfs_raw; + readonly_partitions[3] = part_spiffs; +} + +#if CONFIG_IDF_TARGET_ESP32 +#define TARGET_CRYPT_CNT_EFUSE ESP_EFUSE_FLASH_CRYPT_CNT +#define TARGET_CRYPT_CNT_WIDTH 7 +#else +#define TARGET_CRYPT_CNT_EFUSE ESP_EFUSE_SPI_BOOT_CRYPT_CNT +#define TARGET_CRYPT_CNT_WIDTH 3 +#endif + +static void example_print_flash_encryption_status(void) +{ + uint32_t flash_crypt_cnt = 0; + esp_efuse_read_field_blob(TARGET_CRYPT_CNT_EFUSE, &flash_crypt_cnt, TARGET_CRYPT_CNT_WIDTH); + printf("FLASH_CRYPT_CNT eFuse value is %" PRIu32 "\n", flash_crypt_cnt); + + esp_flash_enc_mode_t mode = esp_get_flash_encryption_mode(); + if (mode == ESP_FLASH_ENC_MODE_DISABLED) { + printf("Flash encryption feature is disabled\n"); + } else { + printf("Flash encryption feature is enabled in %s mode\n", + mode == ESP_FLASH_ENC_MODE_DEVELOPMENT ? "DEVELOPMENT" : "RELEASE"); + } +} + +void app_main(void) +{ + example_print_flash_encryption_status(); + fill_array_of_readonly_data_partitions(); + unity_run_menu(); +} + +TEST_CASE("Read-only partition - SPI flash API", "[spi_flash]") +{ + esp_err_t err; + char buf[11] = {0}; + const char some_data[] = "0123456789"; + for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) { + const esp_partition_t *part = readonly_partitions[i]; + // Writing to the SPI flash on address overlapping read-only partition shouldn't be possible + // and should return ESP_ERR_NOT_ALLOWED error + err = esp_flash_write(part->flash_chip, some_data, part->address, strlen(some_data)); + ESP_LOGD(TAG, "Writing %u bytes to partition %s at 0x%lx, should return %s and returned %s (0x%x)", + strlen(some_data), part->label, part->address, esp_err_to_name(ESP_ERR_NOT_ALLOWED), esp_err_to_name(err), err); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err); + + // Reading the SPI flash on address overlapping read-only partition should be possible without an error + TEST_ESP_OK(esp_flash_read(part->flash_chip, &buf, part->address, strlen(some_data))); + } +} + +TEST_CASE("Read-only partition - Partition API", "[partition]") +{ + esp_err_t err; + // Writing to the partition should not be possible and should return ESP_ERR_NOT_ALLOWED error + const char some_data[] = "0123456789"; + for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) { + err = esp_partition_write(readonly_partitions[i], 0, some_data, strlen(some_data)); + ESP_LOGD(TAG, "esp_partition_write on readonly_partitions[%d] should return %s and returned %s (0x%x)", + i, esp_err_to_name(ESP_ERR_NOT_ALLOWED), esp_err_to_name(err), err); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err); + } + + // Reading the partition should be possible without an error + char buf[strlen(some_data)]; + for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) { + err = esp_partition_read(readonly_partitions[i], 0, buf, sizeof(buf)); + TEST_ESP_OK(err); + } +} + +TEST_CASE("Read-only partition - NVS API", "[nvs]") +{ + nvs_handle_t handle; + esp_err_t err; + + err = nvs_flash_init_partition(nvs_partition_name); + TEST_ESP_OK(err); + + // NVS partition flagged as read-only should be possible to open in read-only mode + err = nvs_open_from_partition(nvs_partition_name, "storage", NVS_READONLY, &handle); + TEST_ESP_OK(err); + + // Read test + int32_t i32_val = 0; + err = nvs_get_i32(handle, "i32_key", &i32_val); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL(-2147483648, i32_val); + nvs_close(handle); + + // NVS partition flagged as read-only shouln't be possible to open in read-write mode + err = nvs_open_from_partition(nvs_partition_name, "storage", NVS_READWRITE, &handle); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err); + nvs_close(handle); +} + +void test_c_api_common(const char* base_path) +{ + char hello_txt[64]; + char new_txt[64]; + snprintf(hello_txt, sizeof(hello_txt), "%s%s", base_path, "/hello.txt"); + snprintf(new_txt, sizeof(new_txt), "%s%s", base_path, "/new.txt"); + + FILE *f; + int fd, status; + char buf[CMP_STRING_LEN] = {0}; + + // Test write mode is not possible + f = fopen(hello_txt, "w"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_WRONLY, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "w+"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_RDWR, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "a"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_WRONLY|O_APPEND, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "a+"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_CREAT|O_RDWR|O_APPEND, 0666); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + f = fopen(hello_txt, "r+"); + TEST_ASSERT_NULL(f); + fd = open(hello_txt, O_RDWR); + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + fd = creat(new_txt, 0666); // == open(new_txt, O_WRONLY|O_CREAT|O_TRUNC, 0666) + TEST_ASSERT_EQUAL(-1, fd); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = link(hello_txt, new_txt); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = rename(hello_txt, new_txt); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = unlink(hello_txt); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + status = truncate(hello_txt, 10); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + // Test read is still possible + fd = open(hello_txt, O_RDONLY); + TEST_ASSERT_GREATER_THAN(0, fd); + + status = ftruncate(fd, 10); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + close(fd); + + f = fopen(hello_txt, "r"); + TEST_ASSERT_NOT_NULL(f); + fread(buf, 1, sizeof(buf) - 1, f); + ESP_LOGD(TAG, "Read from file: %s", buf); + TEST_ASSERT_EQUAL(0, strcmp(buf, cmp_string)); + memset(buf, 0, sizeof(buf)); + + char str[] = "Should not be written"; + fseek(f, 0, SEEK_SET); + status = fwrite(str, 1, sizeof(str), f); // Writing should do nothing + TEST_ASSERT_EQUAL(0, status); + TEST_ASSERT_EQUAL(EBADF, errno); + + fread(buf, 1, sizeof(buf) - 1, f); + ESP_LOGD(TAG, "Read from file: %s", buf); + TEST_ASSERT_EQUAL(0, strcmp(buf, cmp_string)); // Test if the file content is still the same + fclose(f); +} + +TEST_CASE("Read-only partition - C file I/O API (using FATFS WL)", "[vfs][fatfs]") +{ + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = false, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE + }; + + esp_err_t err; + int status; + + err = esp_vfs_fat_spiflash_mount_rw_wl(FATFS_WL_BASE_PATH, fatfs_wl_partition_name, &mount_config, &s_wl_handle); + TEST_ESP_OK(err); + + // FATFS WL itself is read-write capable, but we are restricting it to read-only mode via esp_partition layer + // Opening a file in a write mode on read-only partition is checked in vfs + + test_c_api_common(FATFS_WL_BASE_PATH); + + // Test directories + DIR *dir; + status = mkdir(FATFS_WL_BASE_PATH "/dir1", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_WL_BASE_PATH "/dir1"); + TEST_ASSERT_NULL(dir); + + status = rmdir(FATFS_WL_BASE_PATH "/dir"); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_WL_BASE_PATH "/dir"); + TEST_ASSERT_NOT_NULL(dir); + closedir(dir); + + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl(FATFS_WL_BASE_PATH, s_wl_handle)); +} + +TEST_CASE("Read-only partition - C file I/O API (using FATFS RAW)", "[vfs][fatfs]") +{ + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = false, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE + }; + + esp_err_t err; + int status; + + err = esp_vfs_fat_spiflash_mount_ro(FATFS_RAW_BASE_PATH, fatfs_raw_partition_name, &mount_config); + TEST_ESP_OK(err); + + // FATFS RAW is read-only itself, but esp_parition read-only adds another layer + // Opening a file in a write mode on read-only partition is checked in vfs + + test_c_api_common(FATFS_RAW_BASE_PATH); + + // Test directories + DIR *dir; + status = mkdir(FATFS_RAW_BASE_PATH "/dir1", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_RAW_BASE_PATH "/dir1"); + TEST_ASSERT_NULL(dir); + + status = rmdir(FATFS_RAW_BASE_PATH "/dir"); + TEST_ASSERT_EQUAL(-1, status); + TEST_ASSERT_EQUAL(EROFS, errno); + + dir = opendir(FATFS_RAW_BASE_PATH "/dir"); + TEST_ASSERT_NOT_NULL(dir); + closedir(dir); + + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_ro(FATFS_RAW_BASE_PATH, fatfs_raw_partition_name)); +} + +TEST_CASE("Read-only partition - C file I/O API (using SPIFFS)", "[vfs][spiffs]") +{ + esp_vfs_spiffs_conf_t conf = { + .base_path = SPIFFS_BASE_PATH, + .partition_label = spiffs_partition_name, + .max_files = 5, + .format_if_mount_failed = false + }; + + esp_err_t err; + err = esp_vfs_spiffs_register(&conf); + TEST_ESP_OK(err); + + // SPIFFS is read-write capable, but we are restricting it to read-only mode via esp_partition layer + + test_c_api_common(SPIFFS_BASE_PATH); + + // SPIFFS doesn't support directories + + TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_partition_name)); +} diff --git a/tools/test_apps/storage/partition_table_readonly/nvs_data.csv b/tools/test_apps/storage/partition_table_readonly/nvs_data.csv new file mode 100644 index 000000000000..8e0cc3569589 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/nvs_data.csv @@ -0,0 +1,13 @@ +# Sample csv file +key,type,encoding,value +storage,namespace,, +u8_key,data,u8,255 +i8_key,data,i8,-128 +u16_key,data,u16,65535 +u32_key,data,u32,4294967295 +i32_key,data,i32,-2147483648 +str_key,data,string,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Fusce quis risus justo. +Suspendisse egestas in nisi sit amet auctor. +Pellentesque rhoncus dictum sodales. +In justo erat, viverra at interdum eget, interdum vel dui." diff --git a/tools/test_apps/storage/partition_table_readonly/partitions_example.csv b/tools/test_apps/storage/partition_table_readonly/partitions_example.csv new file mode 100644 index 000000000000..3f34137c61d9 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/partitions_example.csv @@ -0,0 +1,9 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs_ro, data, nvs, , 0x4000, readonly +nvs_key, data, nvs_keys, , 0x1000, encrypted +phy_init, data, phy, , 0x1000, +factory, app, factory, , 0x60000, +fatfs_ro, data, fat, , 528K, readonly +fatfs_raw_ro, data, fat, , 528K, encrypted:readonly +spiffs_ro, data, spiffs, , 256K, readonly diff --git a/tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py b/tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py new file mode 100644 index 000000000000..79ce7443cc12 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'default', + ], + indirect=True) +def test_partition_table_readonly(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=120) + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.flash_encryption +@pytest.mark.parametrize( + 'config', + [ + 'encrypted', + ], + indirect=True) +def test_partition_table_readonly_flash_encryption(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=120) diff --git a/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default new file mode 100644 index 000000000000..5520a5507226 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default @@ -0,0 +1 @@ +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y diff --git a/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted new file mode 100644 index 000000000000..4cfe55cc8169 --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted @@ -0,0 +1,9 @@ +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y +CONFIG_SECURE_FLASH_ENC_ENABLED=y +CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y +CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y +CONFIG_SECURE_BOOT_ALLOW_JTAG=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y +CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y diff --git a/tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults b/tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults new file mode 100644 index 000000000000..92285035637b --- /dev/null +++ b/tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults @@ -0,0 +1,20 @@ +# General options for additional checks +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y +CONFIG_NVS_ASSERT_ERROR_CHECK=y + +# Disable task watchdog since this app uses an interactive menu +CONFIG_ESP_TASK_WDT_INIT=n + +# SPIFFS configuration +CONFIG_SPIFFS_USE_MTIME=n # Disable mtime update as the partition is read-only + +# Custom partition table related +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y From 37dbce301e05dd872a5598fb4723d3b02293d38b Mon Sep 17 00:00:00 2001 From: Shang Zhou Date: Sat, 7 Oct 2023 10:37:35 +0800 Subject: [PATCH 3/3] docs: Provide CN translation for partition-tables.rst --- docs/en/api-guides/partition-tables.rst | 8 ++++---- docs/zh_CN/api-guides/partition-tables.rst | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/en/api-guides/partition-tables.rst b/docs/en/api-guides/partition-tables.rst index ea77d5183fe1..d096aa9eac8b 100644 --- a/docs/en/api-guides/partition-tables.rst +++ b/docs/en/api-guides/partition-tables.rst @@ -166,19 +166,19 @@ If you want the partitions in the partition table to work relative to any placem Flags ~~~~~ -Two flags are currently supported, ``encrypted`` and ``readonly``: - +Two flags are currently supported, ``encrypted`` and ``readonly``: + - If ``encrypted`` flag is set, the partition will be encrypted if :doc:`/security/flash-encryption` is enabled. .. note:: ``app`` type partitions will always be encrypted, regardless of whether this flag is set or not. - - If ``readonly`` flag is set, the partition will be read-only. This flag is only supported for ``data`` type partitions except ``ota``` and ``coredump``` subtypes. This flag can help to protect against the accidental writes to partition that contains critical device specific configuration data, e.g., factory data partition. + - If ``readonly`` flag is set, the partition will be read-only. This flag is only supported for ``data`` type partitions except ``ota``` and ``coredump``` subtypes. This flag can help to protect against accidental writes to a partition that contains critical device-specific configuration data, e.g., factory data partition. .. note:: - Using C file I/O API to open a file (``fopen```) in any write mode (``w``, ``w+``, ``a``, ``a+``, ``r+``) will fail and return ``NULL``. Using ``open`` with any other flag than ``O_RDONLY`` will fail and return ``-1`` while ``errno`` global variable will be set to ``EACCES``. This is also true for any other POSIX syscall function performing write or erase operations. Opening a handle in read-write mode for NVS on a read-only partition will fail and return :c:macro:`ESP_ERR_NOT_ALLOWED` error code. Using a lower level API like ``esp_partition``, ``spi_flash``, ``wear_levelling``, etc. to write to a read-only partition will result in :c:macro:`ESP_ERR_NOT_ALLOWED` error code. + Using C file I/O API to open a file (``fopen```) in any write mode (``w``, ``w+``, ``a``, ``a+``, ``r+``) will fail and return ``NULL``. Using ``open`` with any other flag than ``O_RDONLY`` will fail and return ``-1`` while ``errno`` global variable will be set to ``EROFS``. This is also true for any other POSIX syscall function performing write or erase operations. Opening a handle in read-write mode for NVS on a read-only partition will fail and return :c:macro:`ESP_ERR_NOT_ALLOWED` error code. Using a lower level API like ``esp_partition``, ``spi_flash``, etc. to write to a read-only partition will result in :c:macro:`ESP_ERR_NOT_ALLOWED` error code. You can specify multiple flags by separating them with a colon. For example, ``encrypted:readonly``. diff --git a/docs/zh_CN/api-guides/partition-tables.rst b/docs/zh_CN/api-guides/partition-tables.rst index d3bda0a0ac70..a62915c6f661 100644 --- a/docs/zh_CN/api-guides/partition-tables.rst +++ b/docs/zh_CN/api-guides/partition-tables.rst @@ -166,11 +166,21 @@ app 分区的大小和偏移地址可以采用十进制数、以 0x 为前缀的 Flags 字段 ~~~~~~~~~~ -当前仅支持 ``encrypted`` 标记。如果 Flags 字段设置为 ``encrypted``,且已启用 :doc:`/security/flash-encryption` 功能,则该分区将会被加密。 +目前支持 ``encrypted`` 和 ``readonly`` 标记: -.. note:: + - 如果 Flags 字段设置为 ``encrypted``,且已启用 :doc:`/security/flash-encryption` 功能,则该分区将会被加密。 + + .. note:: + + 无论是否设置 Flags 字段,``app`` 分区都将保持加密。 + + - 如果 Flags 字段设置为 ``readonly``,则该分区为只读分区。``readonly`` 标记仅支持除 ``ota`` 和 ``coredump`` 子类型外的 ``data`` 分区。使用该标记,防止意外写入如出厂数据分区等包含关键设备特定配置数据的分区。 + + .. note:: + + 在任何写入模式下 (``w``、``w+``、``a``、``a+``、``r+``),尝试通过 C 文件 I/O API 打开文件 (``fopen```) 的操作都将失败并返回 ``NULL``。除 ``O_RDONLY`` 外,``open`` 与任何标志一同使用都将失败并返回 ``-1``,全局变量 ``errno`` 也将设置为 ``EROFS``。上述情况同样适用于通过其他 POSIX 系统调用函数执行写入或擦除的操作。在只读分区上,以读写模式打开 NVS 的句柄将失败并返回 :c:macro:`ESP_ERR_NOT_ALLOWED` 错误代码,使用 ``esp_partition`` 或 ``spi_flash`` 等较低级别的 API 进行写入操作也将返回 :c:macro:`ESP_ERR_NOT_ALLOWED` 错误代码。 - ``app`` 分区始终会被加密,不管 Flags 字段是否设置。 +可以使用冒号连接不同的标记,来同时指定多个标记,如 ``encrypted:readonly``。 生成二进制分区表 ----------------