Przeglądaj źródła

Merge branch 'feature/partition_readonly_flag' into 'master'

feat(partition_table): Add read-only partition flag and functionality

Closes IDF-6421

See merge request espressif/esp-idf!24855
Martin Vychodil 2 lat temu
rodzic
commit
64befdca3a
50 zmienionych plików z 852 dodań i 172 usunięć
  1. 11 1
      components/bootloader_support/include/esp_flash_partitions.h
  2. 1 0
      components/esp_common/include/esp_err.h
  3. 3 0
      components/esp_common/src/esp_err_to_name.c
  4. 4 0
      components/esp_partition/include/esp_partition.h
  5. 2 1
      components/esp_partition/partition.c
  6. 6 0
      components/esp_partition/partition_linux.c
  7. 28 3
      components/esp_partition/partition_target.c
  8. 1 1
      components/fatfs/diskio/diskio_wl.c
  9. 12 2
      components/fatfs/vfs/vfs_fat_spiflash.c
  10. 6 1
      components/nvs_flash/host_test/fixtures/test_fixtures.hpp
  11. 2 0
      components/nvs_flash/include/nvs.h
  12. 1 0
      components/nvs_flash/include/nvs_handle.hpp
  13. 10 13
      components/nvs_flash/src/nvs_partition.cpp
  14. 6 1
      components/nvs_flash/src/nvs_partition.hpp
  15. 6 1
      components/nvs_flash/src/nvs_partition_manager.cpp
  16. 10 13
      components/nvs_flash/src/partition.hpp
  17. 13 13
      components/nvs_flash/test_nvs_host/test_fixtures.hpp
  18. 10 3
      components/partition_table/gen_esp32part.py
  19. 15 10
      components/partition_table/parttool.py
  20. 14 13
      components/spi_flash/esp_flash_api.c
  21. 6 2
      components/spi_flash/include/esp_flash.h
  22. 6 1
      components/spi_flash/spi_flash_os_func_app.c
  23. 18 12
      components/spiffs/esp_spiffs.c
  24. 7 2
      components/vfs/include/esp_vfs.h
  25. 62 1
      components/vfs/vfs.c
  26. 6 1
      components/wear_levelling/Partition.cpp
  27. 4 3
      components/wear_levelling/WL_Ext_Perf.cpp
  28. 2 2
      components/wear_levelling/WL_Ext_Safe.cpp
  29. 52 51
      components/wear_levelling/WL_Flash.cpp
  30. 1 0
      components/wear_levelling/private_include/Partition.h
  31. 2 1
      components/wear_levelling/private_include/WL_Ext_Perf.h
  32. 2 1
      components/wear_levelling/private_include/WL_Ext_Safe.h
  33. 5 4
      components/wear_levelling/private_include/WL_Flash.h
  34. 8 6
      components/wear_levelling/wear_levelling.cpp
  35. 13 3
      docs/en/api-guides/partition-tables.rst
  36. 13 3
      docs/zh_CN/api-guides/partition-tables.rst
  37. 0 3
      tools/ci/check_copyright_ignore.txt
  38. 17 0
      tools/test_apps/.build-test-rules.yml
  39. 6 0
      tools/test_apps/storage/partition_table_readonly/CMakeLists.txt
  40. 2 0
      tools/test_apps/storage/partition_table_readonly/README.md
  41. 0 0
      tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt
  42. 1 0
      tools/test_apps/storage/partition_table_readonly/filesystem_image/hello.txt
  43. 16 0
      tools/test_apps/storage/partition_table_readonly/main/CMakeLists.txt
  44. 359 0
      tools/test_apps/storage/partition_table_readonly/main/main.c
  45. 13 0
      tools/test_apps/storage/partition_table_readonly/nvs_data.csv
  46. 9 0
      tools/test_apps/storage/partition_table_readonly/partitions_example.csv
  47. 31 0
      tools/test_apps/storage/partition_table_readonly/pytest_partition_table_readonly.py
  48. 1 0
      tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default
  49. 9 0
      tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.encrypted
  50. 20 0
      tools/test_apps/storage/partition_table_readonly/sdkconfig.defaults

+ 11 - 1
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.

+ 1 - 0
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 */

+ 3 - 0
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

+ 4 - 0
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,

+ 2 - 1
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)

+ 6 - 0
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;
     }

+ 28 - 3
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;
 }

+ 1 - 1
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
  */

+ 12 - 2
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 <string.h>
 #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:

+ 6 - 1
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 @@ public:
         return size;
     }
 
+    bool get_readonly() override
+    {
+        return partition.readonly;
+    }
+
     const esp_partition_t partition;
 
 private:

+ 2 - 0
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);

+ 1 - 0
components/nvs_flash/include/nvs_handle.hpp

@@ -222,6 +222,7 @@ protected:
  *             - 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

+ 10 - 13
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 <cstdlib>
 #include "nvs_partition.hpp"
@@ -74,4 +66,9 @@ uint32_t NVSPartition::get_size()
     return mESPPartition->size;
 }
 
+bool NVSPartition::get_readonly()
+{
+    return mESPPartition->readonly;
+}
+
 } // nvs

+ 6 - 1
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 @@ public:
      */
     uint32_t get_size() override;
 
+    /**
+     * @return true if the partition is read-only.
+     */
+    bool get_readonly() override;
+
 protected:
     const esp_partition_t* mESPPartition;
 };

+ 6 - 1
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<Partition*>(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;

+ 10 - 13
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 @@ public:
      * 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

+ 13 - 13
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 @@ public:
         assert(partition_name);
         assert(flash_emu);
         assert(size);
+        readonly = false;
     }
 
     const char *get_partition_name() override
@@ -101,6 +94,11 @@ public:
         return size;
     }
 
+    bool get_readonly() override
+    {
+        return readonly;
+    }
+
 private:
     const char *partition_name;
 
@@ -109,6 +107,8 @@ private:
     uint32_t address;
 
     uint32_t size;
+
+    bool readonly;
 };
 
 struct PartitionEmulationFixture {

+ 10 - 3
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_NONE = None
 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 @@ class PartitionDefinition(object):
         self.offset = None
         self.size = None
         self.encrypted = False
+        self.readonly = False
 
     @classmethod
     def from_csv(cls, line, line_no):
@@ -454,6 +456,11 @@ class PartitionDefinition(object):
             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

+ 15 - 10
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 tempfile
 
 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 @@ class ParttoolTarget():
         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]

+ 14 - 13
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)
 
 

+ 6 - 2
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);
 

+ 6 - 1
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;

+ 18 - 12
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) {

+ 7 - 2
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 */

+ 62 - 1
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;

+ 6 - 1
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()
 {
 

+ 4 - 3
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 <stdlib.h>
 #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()

+ 2 - 2
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*/

+ 52 - 51
components/wear_levelling/WL_Flash.cpp

@@ -1,11 +1,12 @@
 /*
- * 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 <stdio.h>
 #include "esp_random.h"
 #include "esp_log.h"
+#include "Partition.h"
 #include "WL_Flash.h"
 #include <stdlib.h>
 #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()
 {

+ 1 - 0
components/wear_levelling/private_include/Partition.h

@@ -32,6 +32,7 @@ public:
     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:

+ 2 - 1
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 @@ public:
     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;

+ 2 - 1
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 @@ public:
     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;

+ 5 - 4
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 *partition);
     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 @@ protected:
     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;

+ 8 - 6
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
  */
@@ -174,12 +174,14 @@ 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
-        Flash_Access *drv = s_instances[handle].instance->get_drv();
-        drv->~Flash_Access();
-        free(drv);
+        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();
         free(s_instances[handle].instance);
         s_instances[handle].instance = NULL;

+ 13 - 3
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``:
 
-.. note::
+  - 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 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 ``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.
 
-    ``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
 ---------------------------------

+ 13 - 3
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``
 
 生成二进制分区表
 ----------------

+ 0 - 3
tools/ci/check_copyright_ignore.txt

@@ -590,16 +590,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

+ 17 - 0
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"

+ 6 - 0
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)

+ 2 - 0
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 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |

+ 0 - 0
tools/test_apps/storage/partition_table_readonly/filesystem_image/dir/dirf.txt


+ 1 - 0
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

+ 16 - 0
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)

+ 359 - 0
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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <esp_log.h>
+#include <esp_attr.h>
+#include "esp_flash.h"
+#include <esp_partition.h>
+#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));
+}

+ 13 - 0
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."

+ 9 - 0
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

+ 31 - 0
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)

+ 1 - 0
tools/test_apps/storage/partition_table_readonly/sdkconfig.ci.default

@@ -0,0 +1 @@
+CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y

+ 9 - 0
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

+ 20 - 0
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