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

This commit is contained in:
Adam Múdry 2023-07-17 11:59:28 +02:00
parent f083570af6
commit ab1eb37fe8
43 changed files with 763 additions and 104 deletions

View File

@ -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.

View File

@ -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 */

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
*/

View File

@ -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:

View File

@ -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:

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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 */

View File

@ -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;

View File

@ -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
*/

View File

@ -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;

View File

@ -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();

View File

@ -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.
``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.
You can specify multiple flags by separating them with a colon. For example, ``encrypted:readonly``.
Generating Binary Partition Table
---------------------------------

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |

View File

@ -0,0 +1 @@
This is a file cointained in the generated filesystem image on the host and flashed to the ESP device

View File

@ -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)

View File

@ -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));
}

View File

@ -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."
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -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
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs_ro, data, nvs, , 0x4000, readonly
4 nvs_key, data, nvs_keys, , 0x1000, encrypted
5 phy_init, data, phy, , 0x1000,
6 factory, app, factory, , 0x60000,
7 fatfs_ro, data, fat, , 528K, readonly
8 fatfs_raw_ro, data, fat, , 528K, encrypted:readonly
9 spiffs_ro, data, spiffs, , 256K, readonly

View File

@ -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)

View File

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

View File

@ -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

View File

@ -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