mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(wear_levelling): Added BDL support
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BDL_Access.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const char *TAG = "wl_bdl_access";
|
||||||
|
|
||||||
|
BDL_Access::BDL_Access(esp_blockdev_handle_t bdl_device)
|
||||||
|
: bdl_device(bdl_device)
|
||||||
|
{
|
||||||
|
assert(bdl_device != ESP_BLOCKDEV_HANDLE_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BDL_Access::get_flash_size()
|
||||||
|
{
|
||||||
|
assert(this->bdl_device->geometry.disk_size <= SIZE_MAX);
|
||||||
|
return (size_t)this->bdl_device->geometry.disk_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t BDL_Access::erase_sector(size_t sector)
|
||||||
|
{
|
||||||
|
size_t erase_size = this->bdl_device->geometry.erase_size;
|
||||||
|
return this->bdl_device->ops->erase(this->bdl_device, sector * erase_size, erase_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t BDL_Access::erase_range(size_t start_address, size_t size)
|
||||||
|
{
|
||||||
|
ESP_LOGV(TAG, "%s - start_address=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)start_address, (uint32_t)size);
|
||||||
|
return this->bdl_device->ops->erase(this->bdl_device, start_address, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t BDL_Access::write(size_t dest_addr, const void *src, size_t size)
|
||||||
|
{
|
||||||
|
ESP_LOGV(TAG, "%s - dest_addr=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)dest_addr, (uint32_t)size);
|
||||||
|
return this->bdl_device->ops->write(this->bdl_device, (const uint8_t *)src, dest_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t BDL_Access::read(size_t src_addr, void *dest, size_t size)
|
||||||
|
{
|
||||||
|
ESP_LOGV(TAG, "%s - src_addr=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)src_addr, (uint32_t)size);
|
||||||
|
return this->bdl_device->ops->read(this->bdl_device, (uint8_t *)dest, size, src_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BDL_Access::get_sector_size()
|
||||||
|
{
|
||||||
|
return this->bdl_device->geometry.erase_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BDL_Access::is_readonly()
|
||||||
|
{
|
||||||
|
return this->bdl_device->device_flags.read_only;
|
||||||
|
}
|
||||||
|
|
||||||
|
BDL_Access::~BDL_Access()
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
idf_component_register(SRCS "Partition.cpp"
|
idf_component_register(SRCS "Partition.cpp"
|
||||||
|
"BDL_Access.cpp"
|
||||||
"SPI_Flash.cpp"
|
"SPI_Flash.cpp"
|
||||||
"WL_Ext_Perf.cpp"
|
"WL_Ext_Perf.cpp"
|
||||||
"WL_Ext_Safe.cpp"
|
"WL_Ext_Safe.cpp"
|
||||||
"WL_Flash.cpp"
|
"WL_Flash.cpp"
|
||||||
"crc32.cpp"
|
"crc32.cpp"
|
||||||
"wear_levelling.cpp"
|
"wear_levelling.cpp"
|
||||||
|
"wl_blockdev.cpp"
|
||||||
INCLUDE_DIRS include
|
INCLUDE_DIRS include
|
||||||
PRIV_INCLUDE_DIRS private_include
|
PRIV_INCLUDE_DIRS private_include
|
||||||
REQUIRES esp_partition
|
REQUIRES esp_partition esp_blockdev
|
||||||
PRIV_REQUIRES spi_flash)
|
PRIV_REQUIRES spi_flash)
|
||||||
|
|
||||||
if(CONFIG_COMPILER_STATIC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # TODO IDF-10089
|
if(CONFIG_COMPILER_STATIC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # TODO IDF-10089
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "Partition.h"
|
#include "Partition.h"
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
static const char *TAG = "wl_partition";
|
static const char *TAG = "wl_partition";
|
||||||
|
|
||||||
@@ -22,34 +22,25 @@ size_t Partition::get_flash_size()
|
|||||||
|
|
||||||
esp_err_t Partition::erase_sector(size_t sector)
|
esp_err_t Partition::erase_sector(size_t sector)
|
||||||
{
|
{
|
||||||
esp_err_t result = ESP_OK;
|
return erase_range(sector * this->partition->erase_size, this->partition->erase_size);
|
||||||
result = erase_range(sector * this->partition->erase_size, this->partition->erase_size);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t Partition::erase_range(size_t start_address, size_t size)
|
esp_err_t Partition::erase_range(size_t start_address, size_t size)
|
||||||
{
|
{
|
||||||
esp_err_t result = esp_partition_erase_range(this->partition, start_address, size);
|
ESP_LOGV(TAG, "%s - start_address=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)start_address, (uint32_t)size);
|
||||||
if (result == ESP_OK) {
|
return esp_partition_erase_range(this->partition, start_address, size);
|
||||||
ESP_LOGV(TAG, "erase_range - start_address=0x%08" PRIx32 ", size=0x%08" PRIx32 ", result=0x%08x", (uint32_t) start_address, (uint32_t) size, result);
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "erase_range - start_address=0x%08" PRIx32 ", size=0x%08" PRIx32 ", result=0x%08x", (uint32_t) start_address, (uint32_t) size, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t Partition::write(size_t dest_addr, const void *src, size_t size)
|
esp_err_t Partition::write(size_t dest_addr, const void *src, size_t size)
|
||||||
{
|
{
|
||||||
esp_err_t result = ESP_OK;
|
ESP_LOGV(TAG, "%s - dest_addr=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)dest_addr, (uint32_t)size);
|
||||||
result = esp_partition_write(this->partition, dest_addr, src, size);
|
return esp_partition_write(this->partition, dest_addr, src, size);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t Partition::read(size_t src_addr, void *dest, size_t size)
|
esp_err_t Partition::read(size_t src_addr, void *dest, size_t size)
|
||||||
{
|
{
|
||||||
esp_err_t result = ESP_OK;
|
ESP_LOGV(TAG, "%s - src_addr=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)src_addr, (uint32_t)size);
|
||||||
result = esp_partition_read(this->partition, src_addr, dest, size);
|
return esp_partition_read(this->partition, src_addr, dest, size);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Partition::get_sector_size()
|
size_t Partition::get_sector_size()
|
||||||
@@ -64,5 +55,4 @@ bool Partition::is_readonly()
|
|||||||
|
|
||||||
Partition::~Partition()
|
Partition::~Partition()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
#include "WL_Ext_Perf.h"
|
#include "WL_Ext_Perf.h"
|
||||||
#include "Partition.h"
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
@@ -27,7 +26,7 @@ WL_Ext_Perf::~WL_Ext_Perf()
|
|||||||
free(this->sector_buffer);
|
free(this->sector_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t WL_Ext_Perf::config(WL_Config_s *cfg, Partition *partition)
|
esp_err_t WL_Ext_Perf::config(WL_Config_s *cfg, Flash_Access *partition)
|
||||||
{
|
{
|
||||||
wl_ext_cfg_t *ext_cfg = (wl_ext_cfg_t *)cfg;
|
wl_ext_cfg_t *ext_cfg = (wl_ext_cfg_t *)cfg;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ WL_Ext_Safe::~WL_Ext_Safe()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t WL_Ext_Safe::config(WL_Config_s *cfg, Partition *partition)
|
esp_err_t WL_Ext_Safe::config(WL_Config_s *cfg, Flash_Access *partition)
|
||||||
{
|
{
|
||||||
esp_err_t result = ESP_OK;
|
esp_err_t result = ESP_OK;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "esp_random.h"
|
#include "esp_random.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "Partition.h"
|
|
||||||
#include "WL_Flash.h"
|
#include "WL_Flash.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "crc32.h"
|
#include "crc32.h"
|
||||||
@@ -39,7 +38,7 @@ WL_Flash::~WL_Flash()
|
|||||||
free(this->temp_buff);
|
free(this->temp_buff);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t WL_Flash::config(wl_config_t *cfg, Partition *partition)
|
esp_err_t WL_Flash::config(wl_config_t *cfg, Flash_Access *partition)
|
||||||
{
|
{
|
||||||
ESP_LOGV(TAG, "%s partition_start_addr=0x%08" PRIx32 ", wl_partition_size=0x%08" PRIx32 ", wl_page_size=0x%08" PRIx32 ", flash_sector_size=0x%08" PRIx32 ", wl_update_rate=0x%08" PRIx32 ", wl_pos_update_record_size=0x%08" PRIx32 ", version=0x%08" PRIx32 ", wl_temp_buff_size=0x%08" PRIx32 , __func__,
|
ESP_LOGV(TAG, "%s partition_start_addr=0x%08" PRIx32 ", wl_partition_size=0x%08" PRIx32 ", wl_page_size=0x%08" PRIx32 ", flash_sector_size=0x%08" PRIx32 ", wl_update_rate=0x%08" PRIx32 ", wl_pos_update_record_size=0x%08" PRIx32 ", version=0x%08" PRIx32 ", wl_temp_buff_size=0x%08" PRIx32 , __func__,
|
||||||
(uint32_t) cfg->wl_partition_start_addr,
|
(uint32_t) cfg->wl_partition_start_addr,
|
||||||
@@ -611,7 +610,7 @@ esp_err_t WL_Flash::read(size_t src_addr, void *dest, size_t size)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Partition *WL_Flash::get_part()
|
Flash_Access *WL_Flash::get_part()
|
||||||
{
|
{
|
||||||
return this->partition;
|
return this->partition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -279,7 +279,7 @@ void calculate_wl_state_address_info(const esp_partition_t *partition, size_t *o
|
|||||||
void calculate_wl_state_crc(WL_State_s *state_ptr)
|
void calculate_wl_state_crc(WL_State_s *state_ptr)
|
||||||
{
|
{
|
||||||
int check_size = WL_STATE_CRC_LEN_V2;
|
int check_size = WL_STATE_CRC_LEN_V2;
|
||||||
// Chech CRC and recover state
|
// Check CRC and recover state
|
||||||
state_ptr->crc32 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)state_ptr, check_size);
|
state_ptr->crc32 = crc32::crc32_le(WL_CFG_CRC_CONST, (uint8_t *)state_ptr, check_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,3 +421,233 @@ TEST_CASE("power down between WL status 1 and WL status 2 update", "[wear_levell
|
|||||||
|
|
||||||
free(tmp_state);
|
free(tmp_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================================== */
|
||||||
|
/* BDL (Block Device Layer) interface tests */
|
||||||
|
/* ======================================================================== */
|
||||||
|
|
||||||
|
TEST_CASE("BDL read/write/erase operations", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->geometry.disk_size > 0);
|
||||||
|
REQUIRE(wl_blockdev->geometry.erase_size > 0);
|
||||||
|
|
||||||
|
const size_t data_size = 256;
|
||||||
|
uint8_t test_data[data_size];
|
||||||
|
uint8_t data_buffer[data_size];
|
||||||
|
size_t target_addr = 3 * 4 * 1024; //"random" address - 3rd sector of the partition (ie anything but 0 sector)
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->erase(wl_blockdev, target_addr, wl_blockdev->geometry.erase_size) == ESP_OK);
|
||||||
|
memset(test_data, 0xFF, data_size);
|
||||||
|
REQUIRE(wl_blockdev->ops->read(wl_blockdev, data_buffer, data_size, target_addr, data_size) == ESP_OK);
|
||||||
|
REQUIRE(memcmp(test_data, data_buffer, data_size) == 0);
|
||||||
|
|
||||||
|
memset(test_data, 'P', data_size);
|
||||||
|
REQUIRE(wl_blockdev->ops->write(wl_blockdev, test_data, target_addr, data_size) == ESP_OK);
|
||||||
|
REQUIRE(wl_blockdev->ops->read(wl_blockdev, data_buffer, data_size, target_addr, data_size) == ESP_OK);
|
||||||
|
REQUIRE(memcmp(test_data, data_buffer, data_size) == 0);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->sync(wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL error paths", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
esp_blockdev_handle_t out = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
CHECK(wl_get_blockdev(ESP_BLOCKDEV_HANDLE_INVALID, &out) == ESP_ERR_INVALID_ARG);
|
||||||
|
CHECK(wl_get_blockdev(ESP_BLOCKDEV_HANDLE_INVALID, NULL) == ESP_ERR_INVALID_ARG);
|
||||||
|
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
CHECK(wl_get_blockdev(part_blockdev, NULL) == ESP_ERR_INVALID_ARG);
|
||||||
|
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL disk_size truncation guard", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
uint64_t orig_size = part_blockdev->geometry.disk_size;
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
|
||||||
|
// Just above the uint32_t boundary
|
||||||
|
part_blockdev->geometry.disk_size = (uint64_t)UINT32_MAX + 1;
|
||||||
|
CHECK(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_ERR_INVALID_SIZE);
|
||||||
|
CHECK(wl_blockdev == ESP_BLOCKDEV_HANDLE_INVALID);
|
||||||
|
|
||||||
|
// Far above the uint32_t boundary
|
||||||
|
wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
part_blockdev->geometry.disk_size = UINT64_MAX;
|
||||||
|
CHECK(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_ERR_INVALID_SIZE);
|
||||||
|
CHECK(wl_blockdev == ESP_BLOCKDEV_HANDLE_INVALID);
|
||||||
|
|
||||||
|
part_blockdev->geometry.disk_size = orig_size;
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL geometry and flags verification", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
CHECK(wl_blockdev->geometry.disk_size > 0);
|
||||||
|
CHECK((uint64_t)wl_blockdev->geometry.disk_size < part_blockdev->geometry.disk_size);
|
||||||
|
CHECK(wl_blockdev->geometry.erase_size > 0);
|
||||||
|
CHECK(wl_blockdev->geometry.read_size == part_blockdev->geometry.read_size);
|
||||||
|
CHECK(wl_blockdev->geometry.write_size == part_blockdev->geometry.write_size);
|
||||||
|
CHECK(wl_blockdev->geometry.recommended_read_size == part_blockdev->geometry.recommended_read_size);
|
||||||
|
CHECK(wl_blockdev->geometry.recommended_write_size == part_blockdev->geometry.recommended_write_size);
|
||||||
|
|
||||||
|
CHECK(wl_blockdev->device_flags.val == part_blockdev->device_flags.val);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops != NULL);
|
||||||
|
CHECK(wl_blockdev->ops->read != NULL);
|
||||||
|
CHECK(wl_blockdev->ops->write != NULL);
|
||||||
|
CHECK(wl_blockdev->ops->erase != NULL);
|
||||||
|
CHECK(wl_blockdev->ops->sync != NULL);
|
||||||
|
CHECK(wl_blockdev->ops->ioctl != NULL);
|
||||||
|
CHECK(wl_blockdev->ops->release != NULL);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL sync operation", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
size_t erase_size = wl_blockdev->geometry.erase_size;
|
||||||
|
REQUIRE(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size) == ESP_OK);
|
||||||
|
|
||||||
|
uint8_t test_data[64];
|
||||||
|
memset(test_data, 'S', sizeof(test_data));
|
||||||
|
REQUIRE(wl_blockdev->ops->write(wl_blockdev, test_data, 0, sizeof(test_data)) == ESP_OK);
|
||||||
|
REQUIRE(wl_blockdev->ops->sync(wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
uint8_t read_data[64];
|
||||||
|
REQUIRE(wl_blockdev->ops->read(wl_blockdev, read_data, sizeof(read_data), 0, sizeof(read_data)) == ESP_OK);
|
||||||
|
REQUIRE(memcmp(test_data, read_data, sizeof(test_data)) == 0);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL ioctl relay to bottom device", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
CHECK(wl_blockdev->ops->ioctl(wl_blockdev, ESP_BLOCKDEV_CMD_MARK_DELETED, NULL) == ESP_ERR_NOT_SUPPORTED);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL erase alignment validation", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
size_t erase_size = wl_blockdev->geometry.erase_size;
|
||||||
|
|
||||||
|
CHECK(wl_blockdev->ops->erase(wl_blockdev, 1, erase_size) == ESP_ERR_INVALID_SIZE);
|
||||||
|
CHECK(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size - 1) == ESP_ERR_INVALID_SIZE);
|
||||||
|
CHECK(wl_blockdev->ops->erase(wl_blockdev, 1, erase_size + 1) == ESP_ERR_INVALID_SIZE);
|
||||||
|
REQUIRE(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size) == ESP_OK);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL read buffer size validation", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
uint8_t buf[64];
|
||||||
|
CHECK(wl_blockdev->ops->read(wl_blockdev, buf, 32, 0, 64) == ESP_ERR_INVALID_ARG);
|
||||||
|
REQUIRE(wl_blockdev->ops->read(wl_blockdev, buf, sizeof(buf), 0, sizeof(buf)) == ESP_OK);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BDL release and reconnect", "[wear_levelling][bdl]")
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||||
|
REQUIRE(partition != NULL);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(esp_partition_ptr_get_blockdev(partition, &part_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
size_t erase_size = wl_blockdev->geometry.erase_size;
|
||||||
|
REQUIRE(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size) == ESP_OK);
|
||||||
|
uint8_t test_data[64];
|
||||||
|
memset(test_data, 'R', sizeof(test_data));
|
||||||
|
REQUIRE(wl_blockdev->ops->write(wl_blockdev, test_data, 0, sizeof(test_data)) == ESP_OK);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
REQUIRE(wl_get_blockdev(part_blockdev, &wl_blockdev) == ESP_OK);
|
||||||
|
|
||||||
|
uint8_t read_data[64];
|
||||||
|
REQUIRE(wl_blockdev->ops->read(wl_blockdev, read_data, sizeof(read_data), 0, sizeof(read_data)) == ESP_OK);
|
||||||
|
REQUIRE(memcmp(test_data, read_data, sizeof(test_data)) == 0);
|
||||||
|
|
||||||
|
REQUIRE(wl_blockdev->ops->release(wl_blockdev) == ESP_OK);
|
||||||
|
REQUIRE(part_blockdev->ops->release(part_blockdev) == ESP_OK);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ from pytest_embedded_idf.utils import idf_parametrize
|
|||||||
@pytest.mark.host_test
|
@pytest.mark.host_test
|
||||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||||
def test_wear_levelling_linux(dut: Dut) -> None:
|
def test_wear_levelling_linux(dut: Dut) -> None:
|
||||||
dut.expect_exact('All tests passed', timeout=120)
|
dut.expect_exact('All tests passed', timeout=180)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_partition.h"
|
#include "esp_partition.h"
|
||||||
|
#include "esp_blockdev.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -120,6 +121,39 @@ size_t wl_size(wl_handle_t handle);
|
|||||||
*/
|
*/
|
||||||
size_t wl_sector_size(wl_handle_t handle);
|
size_t wl_sector_size(wl_handle_t handle);
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------- */
|
||||||
|
/* BDL support */
|
||||||
|
/* -------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a wear-levelling BDL instance on top of the given bottom block device.
|
||||||
|
*
|
||||||
|
* All I/O is routed exclusively through @p bdl_bottom_device, keeping the BDL stack
|
||||||
|
* fully independent of the legacy wl_handle_t driver. The underlying device can be
|
||||||
|
* an esp_partition BDL, a memory-backed disk, or any other block device implementation.
|
||||||
|
*
|
||||||
|
* Internally allocates a WL_Flash engine (deriving its configuration from the bottom
|
||||||
|
* device geometry) and a BDL_Access adapter. Releasing the returned BDL handle
|
||||||
|
* flushes and destroys the wear-levelling instance.
|
||||||
|
*
|
||||||
|
* The output device inherits geometry constraints (read_size, write_size) and device
|
||||||
|
* flags (read_only, encrypted, etc.) from @p bdl_bottom_device. The disk_size and
|
||||||
|
* erase_size are determined by the WL engine.
|
||||||
|
*
|
||||||
|
* @note The WL configuration stores partition size as uint32_t. If the bottom device
|
||||||
|
* reports a disk_size exceeding UINT32_MAX the function fails immediately with
|
||||||
|
* ESP_ERR_INVALID_SIZE to prevent silent truncation (fail-fast).
|
||||||
|
*
|
||||||
|
* @param[in] bdl_bottom_device Handle of the bottom block device layer instance (e.g. partition BDL)
|
||||||
|
* @param[out] out_bdl_handle_ptr Handle of the created wear-levelling block device instance
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* - ESP_OK on success
|
||||||
|
* - ESP_ERR_NO_MEM if memory allocation has failed
|
||||||
|
* - ESP_ERR_INVALID_ARG if any of the arguments are invalid
|
||||||
|
* - ESP_ERR_INVALID_SIZE if @p bdl_bottom_device disk_size exceeds UINT32_MAX
|
||||||
|
*/
|
||||||
|
esp_err_t wl_get_blockdev(const esp_blockdev_handle_t bdl_bottom_device, esp_blockdev_handle_t *out_bdl_handle_ptr);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _BDL_Access_H_
|
||||||
|
#define _BDL_Access_H_
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "Flash_Access.h"
|
||||||
|
#include "esp_blockdev.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Flash_Access adapter that delegates all I/O to an esp_blockdev_handle_t.
|
||||||
|
*
|
||||||
|
* Used exclusively by the BDL wear-levelling path so that the core WL_Flash
|
||||||
|
* engine can operate on any block device without knowing about esp_partition.
|
||||||
|
*/
|
||||||
|
class BDL_Access : public Flash_Access
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BDL_Access(esp_blockdev_handle_t bdl_device);
|
||||||
|
|
||||||
|
size_t get_flash_size() override;
|
||||||
|
|
||||||
|
esp_err_t erase_sector(size_t sector) override;
|
||||||
|
esp_err_t erase_range(size_t start_address, size_t size) override;
|
||||||
|
|
||||||
|
esp_err_t write(size_t dest_addr, const void *src, size_t size) override;
|
||||||
|
esp_err_t read(size_t src_addr, void *dest, size_t size) override;
|
||||||
|
|
||||||
|
size_t get_sector_size() override;
|
||||||
|
bool is_readonly() override;
|
||||||
|
|
||||||
|
~BDL_Access() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
esp_blockdev_handle_t bdl_device;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _BDL_Access_H_
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -30,6 +30,11 @@ public:
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
virtual bool is_readonly()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Flash_Access() {};
|
virtual ~Flash_Access() {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -22,21 +22,21 @@ class Partition : public Flash_Access
|
|||||||
public:
|
public:
|
||||||
Partition(const esp_partition_t *partition);
|
Partition(const esp_partition_t *partition);
|
||||||
|
|
||||||
virtual size_t get_flash_size();
|
size_t get_flash_size() override;
|
||||||
|
|
||||||
virtual esp_err_t erase_sector(size_t sector);
|
esp_err_t erase_sector(size_t sector) override;
|
||||||
virtual esp_err_t erase_range(size_t start_address, size_t size);
|
esp_err_t erase_range(size_t start_address, size_t size) override;
|
||||||
|
|
||||||
virtual esp_err_t write(size_t dest_addr, const void *src, size_t size);
|
esp_err_t write(size_t dest_addr, const void *src, size_t size) override;
|
||||||
virtual esp_err_t read(size_t src_addr, void *dest, size_t size);
|
esp_err_t read(size_t src_addr, void *dest, size_t size) override;
|
||||||
|
|
||||||
virtual size_t get_sector_size();
|
size_t get_sector_size() override;
|
||||||
virtual bool is_readonly();
|
bool is_readonly() override;
|
||||||
|
|
||||||
|
~Partition() override;
|
||||||
|
|
||||||
virtual ~Partition();
|
|
||||||
protected:
|
protected:
|
||||||
const esp_partition_t *partition;
|
const esp_partition_t *partition;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _Partition_H_
|
#endif // _Partition_H_
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#ifndef _WL_Ext_Perf_H_
|
#ifndef _WL_Ext_Perf_H_
|
||||||
#define _WL_Ext_Perf_H_
|
#define _WL_Ext_Perf_H_
|
||||||
|
|
||||||
#include "Partition.h"
|
|
||||||
#include "WL_Flash.h"
|
#include "WL_Flash.h"
|
||||||
#include "WL_Ext_Cfg.h"
|
#include "WL_Ext_Cfg.h"
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ public:
|
|||||||
WL_Ext_Perf();
|
WL_Ext_Perf();
|
||||||
~WL_Ext_Perf() override;
|
~WL_Ext_Perf() override;
|
||||||
|
|
||||||
esp_err_t config(WL_Config_s *cfg, Partition *partition) override;
|
esp_err_t config(WL_Config_s *cfg, Flash_Access *partition) override;
|
||||||
esp_err_t init() override;
|
esp_err_t init() override;
|
||||||
|
|
||||||
size_t get_flash_size() override;
|
size_t get_flash_size() override;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#ifndef _WL_Ext_Safe_H_
|
#ifndef _WL_Ext_Safe_H_
|
||||||
#define _WL_Ext_Safe_H_
|
#define _WL_Ext_Safe_H_
|
||||||
|
|
||||||
#include "Partition.h"
|
|
||||||
#include "WL_Flash.h"
|
#include "WL_Flash.h"
|
||||||
#include "WL_Ext_Cfg.h"
|
#include "WL_Ext_Cfg.h"
|
||||||
#include "WL_Ext_Perf.h"
|
#include "WL_Ext_Perf.h"
|
||||||
@@ -17,7 +16,7 @@ public:
|
|||||||
WL_Ext_Safe();
|
WL_Ext_Safe();
|
||||||
~WL_Ext_Safe() override;
|
~WL_Ext_Safe() override;
|
||||||
|
|
||||||
esp_err_t config(WL_Config_s *cfg, Partition *partition) override;
|
esp_err_t config(WL_Config_s *cfg, Flash_Access *partition) override;
|
||||||
esp_err_t init() override;
|
esp_err_t init() override;
|
||||||
|
|
||||||
size_t get_flash_size() override;
|
size_t get_flash_size() override;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "Flash_Access.h"
|
#include "Flash_Access.h"
|
||||||
#include "Partition.h"
|
|
||||||
#include "WL_Config.h"
|
#include "WL_Config.h"
|
||||||
#include "WL_State.h"
|
#include "WL_State.h"
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ public :
|
|||||||
WL_Flash();
|
WL_Flash();
|
||||||
~WL_Flash() override;
|
~WL_Flash() override;
|
||||||
|
|
||||||
virtual esp_err_t config(wl_config_t *cfg, Partition *partition);
|
virtual esp_err_t config(wl_config_t *cfg, Flash_Access *partition);
|
||||||
virtual esp_err_t init();
|
virtual esp_err_t init();
|
||||||
|
|
||||||
size_t get_flash_size() override;
|
size_t get_flash_size() override;
|
||||||
@@ -36,7 +35,7 @@ public :
|
|||||||
|
|
||||||
esp_err_t flush() override;
|
esp_err_t flush() override;
|
||||||
|
|
||||||
Partition *get_part();
|
Flash_Access *get_part();
|
||||||
wl_config_t *get_cfg();
|
wl_config_t *get_cfg();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -44,7 +43,7 @@ protected:
|
|||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
wl_state_t state;
|
wl_state_t state;
|
||||||
wl_config_t cfg;
|
wl_config_t cfg;
|
||||||
Partition *partition = NULL;
|
Flash_Access *partition = NULL;
|
||||||
|
|
||||||
size_t addr_cfg;
|
size_t addr_cfg;
|
||||||
size_t addr_state1;
|
size_t addr_state1;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
idf_component_register(SRCS test_wl.c
|
idf_component_register(SRCS test_wl.c
|
||||||
PRIV_INCLUDE_DIRS .
|
PRIV_INCLUDE_DIRS .
|
||||||
PRIV_REQUIRES wear_levelling unity
|
PRIV_REQUIRES unity
|
||||||
|
REQUIRES wear_levelling
|
||||||
EMBED_FILES test_partition_v1.bin
|
EMBED_FILES test_partition_v1.bin
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -319,12 +319,214 @@ TEST(wear_levelling, version_update)
|
|||||||
}
|
}
|
||||||
#endif // CONFIG_WL_SECTOR_SIZE_4096
|
#endif // CONFIG_WL_SECTOR_SIZE_4096
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_read_write_erase)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
TEST_ASSERT(wl_blockdev->geometry.disk_size > 0);
|
||||||
|
TEST_ASSERT(wl_blockdev->geometry.erase_size > 0);
|
||||||
|
|
||||||
|
const size_t data_size = 256;
|
||||||
|
uint8_t test_data[data_size];
|
||||||
|
uint8_t data_buffer[data_size];
|
||||||
|
const off_t target_addr = 3 * 4 * 1024;
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->erase(wl_blockdev, target_addr, wl_blockdev->geometry.erase_size));
|
||||||
|
memset((void*)test_data, 0xFF, data_size);
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->read(wl_blockdev, data_buffer, data_size, target_addr, data_size));
|
||||||
|
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||||
|
|
||||||
|
memset((void*)test_data, 'P', data_size);
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->write(wl_blockdev, test_data, target_addr, data_size));
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->read(wl_blockdev, data_buffer, data_size, target_addr, data_size));
|
||||||
|
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->sync(wl_blockdev));
|
||||||
|
|
||||||
|
// Release WL BDL (internally unmounts WL)
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_error_paths)
|
||||||
|
{
|
||||||
|
esp_blockdev_handle_t out = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, wl_get_blockdev(ESP_BLOCKDEV_HANDLE_INVALID, &out));
|
||||||
|
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, wl_get_blockdev(ESP_BLOCKDEV_HANDLE_INVALID, NULL));
|
||||||
|
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, wl_get_blockdev(part_blockdev, NULL));
|
||||||
|
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_geometry_and_flags)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
TEST_ASSERT(wl_blockdev->geometry.disk_size > 0);
|
||||||
|
TEST_ASSERT(wl_blockdev->geometry.disk_size < part_blockdev->geometry.disk_size);
|
||||||
|
TEST_ASSERT(wl_blockdev->geometry.erase_size > 0);
|
||||||
|
TEST_ASSERT_EQUAL(part_blockdev->geometry.read_size, wl_blockdev->geometry.read_size);
|
||||||
|
TEST_ASSERT_EQUAL(part_blockdev->geometry.write_size, wl_blockdev->geometry.write_size);
|
||||||
|
TEST_ASSERT_EQUAL(part_blockdev->geometry.recommended_read_size, wl_blockdev->geometry.recommended_read_size);
|
||||||
|
TEST_ASSERT_EQUAL(part_blockdev->geometry.recommended_write_size, wl_blockdev->geometry.recommended_write_size);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(part_blockdev->device_flags.val, wl_blockdev->device_flags.val);
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops);
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops->read);
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops->write);
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops->erase);
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops->sync);
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops->ioctl);
|
||||||
|
TEST_ASSERT_NOT_NULL(wl_blockdev->ops->release);
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_sync)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
size_t erase_size = wl_blockdev->geometry.erase_size;
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size));
|
||||||
|
|
||||||
|
uint8_t test_data[64];
|
||||||
|
memset(test_data, 'S', sizeof(test_data));
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->write(wl_blockdev, test_data, 0, sizeof(test_data)));
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->sync(wl_blockdev));
|
||||||
|
|
||||||
|
uint8_t read_data[64];
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->read(wl_blockdev, read_data, sizeof(read_data), 0, sizeof(read_data)));
|
||||||
|
TEST_ASSERT_EQUAL(0, memcmp(test_data, read_data, sizeof(test_data)));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_ioctl)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED,
|
||||||
|
wl_blockdev->ops->ioctl(wl_blockdev, ESP_BLOCKDEV_CMD_MARK_DELETED, NULL));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_erase_alignment)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
size_t erase_size = wl_blockdev->geometry.erase_size;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, wl_blockdev->ops->erase(wl_blockdev, 1, erase_size));
|
||||||
|
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, wl_blockdev->ops->erase(wl_blockdev, 0, erase_size - 1));
|
||||||
|
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, wl_blockdev->ops->erase(wl_blockdev, 1, erase_size + 1));
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_read_buffer_check)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
uint8_t buf[64];
|
||||||
|
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, wl_blockdev->ops->read(wl_blockdev, buf, 32, 0, 64));
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->read(wl_blockdev, buf, sizeof(buf), 0, sizeof(buf)));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(wear_levelling, test_bdl_release_and_reconnect)
|
||||||
|
{
|
||||||
|
const esp_partition_t *partition = get_test_data_partition();
|
||||||
|
|
||||||
|
esp_blockdev_handle_t part_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(esp_partition_ptr_get_blockdev(partition, &part_blockdev));
|
||||||
|
|
||||||
|
esp_blockdev_handle_t wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
size_t erase_size = wl_blockdev->geometry.erase_size;
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->erase(wl_blockdev, 0, erase_size));
|
||||||
|
uint8_t test_data[64];
|
||||||
|
memset(test_data, 'R', sizeof(test_data));
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->write(wl_blockdev, test_data, 0, sizeof(test_data)));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
|
||||||
|
wl_blockdev = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
TEST_ESP_OK(wl_get_blockdev(part_blockdev, &wl_blockdev));
|
||||||
|
|
||||||
|
uint8_t read_data[64];
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->read(wl_blockdev, read_data, sizeof(read_data), 0, sizeof(read_data)));
|
||||||
|
TEST_ASSERT_EQUAL(0, memcmp(test_data, read_data, sizeof(test_data)));
|
||||||
|
|
||||||
|
TEST_ESP_OK(wl_blockdev->ops->release(wl_blockdev));
|
||||||
|
TEST_ESP_OK(part_blockdev->ops->release(part_blockdev));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_GROUP_RUNNER(wear_levelling)
|
TEST_GROUP_RUNNER(wear_levelling)
|
||||||
{
|
{
|
||||||
RUN_TEST_CASE(wear_levelling, wl_unmount_doesnt_leak_memory)
|
RUN_TEST_CASE(wear_levelling, wl_unmount_doesnt_leak_memory)
|
||||||
RUN_TEST_CASE(wear_levelling, wl_mount_checks_partition_params)
|
RUN_TEST_CASE(wear_levelling, wl_mount_checks_partition_params)
|
||||||
RUN_TEST_CASE(wear_levelling, multiple_tasks_single_handle)
|
RUN_TEST_CASE(wear_levelling, multiple_tasks_single_handle)
|
||||||
RUN_TEST_CASE(wear_levelling, write_doesnt_touch_other_sectors)
|
RUN_TEST_CASE(wear_levelling, write_doesnt_touch_other_sectors)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_read_write_erase)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_error_paths)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_geometry_and_flags)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_sync)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_ioctl)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_erase_alignment)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_read_buffer_check)
|
||||||
|
RUN_TEST_CASE(wear_levelling, test_bdl_release_and_reconnect)
|
||||||
|
|
||||||
#if CONFIG_WL_SECTOR_SIZE_4096
|
#if CONFIG_WL_SECTOR_SIZE_4096
|
||||||
RUN_TEST_CASE(wear_levelling, version_update)
|
RUN_TEST_CASE(wear_levelling, version_update)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <sys/lock.h>
|
#include <sys/lock.h>
|
||||||
#include "wear_levelling.h"
|
#include "wear_levelling.h"
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
#include "WL_Flash.h"
|
#include "WL_Flash.h"
|
||||||
#include "WL_Ext_Perf.h"
|
#include "WL_Ext_Perf.h"
|
||||||
#include "WL_Ext_Safe.h"
|
#include "WL_Ext_Safe.h"
|
||||||
#include "SPI_Flash.h"
|
|
||||||
#include "Partition.h"
|
#include "Partition.h"
|
||||||
|
|
||||||
#ifndef MAX_WL_HANDLES
|
#ifndef MAX_WL_HANDLES
|
||||||
@@ -49,7 +49,22 @@ static wl_instance_t s_instances[MAX_WL_HANDLES];
|
|||||||
static _lock_t s_instances_lock;
|
static _lock_t s_instances_lock;
|
||||||
static const char *TAG = "wear_levelling";
|
static const char *TAG = "wear_levelling";
|
||||||
|
|
||||||
static esp_err_t check_handle(wl_handle_t handle, const char *func);
|
static esp_err_t check_handle(wl_handle_t handle, const char *func)
|
||||||
|
{
|
||||||
|
if (handle == WL_INVALID_HANDLE) {
|
||||||
|
ESP_LOGE(TAG, "%s: invalid handle", func);
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (handle >= MAX_WL_HANDLES) {
|
||||||
|
ESP_LOGE(TAG, "%s: instance[0x%08" PRIx32 "] out of range", func, handle);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
if (s_instances[handle].instance == NULL) {
|
||||||
|
ESP_LOGE(TAG, "%s: instance[0x%08" PRIx32 "] not initialized", func, handle);
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t wl_mount(const esp_partition_t *partition, wl_handle_t *out_handle)
|
esp_err_t wl_mount(const esp_partition_t *partition, wl_handle_t *out_handle)
|
||||||
{
|
{
|
||||||
@@ -170,100 +185,106 @@ out:
|
|||||||
|
|
||||||
esp_err_t wl_unmount(wl_handle_t handle)
|
esp_err_t wl_unmount(wl_handle_t handle)
|
||||||
{
|
{
|
||||||
esp_err_t result = ESP_OK;
|
|
||||||
_lock_acquire(&s_instances_lock);
|
_lock_acquire(&s_instances_lock);
|
||||||
result = check_handle(handle, __func__);
|
|
||||||
|
esp_err_t result = check_handle(handle, __func__);
|
||||||
if (result == ESP_OK) {
|
if (result == ESP_OK) {
|
||||||
// We use placement new in wl_mount, so call destructor directly
|
// Acquire per-instance lock to drain any in-flight I/O that is
|
||||||
Partition *part = s_instances[handle].instance->get_part();
|
// running outside the global lock.
|
||||||
// We have to flush state of the component
|
_lock_acquire(&s_instances[handle].lock);
|
||||||
|
Flash_Access *part = s_instances[handle].instance->get_part();
|
||||||
if (!part->is_readonly()) {
|
if (!part->is_readonly()) {
|
||||||
result = s_instances[handle].instance->flush();
|
result = s_instances[handle].instance->flush();
|
||||||
}
|
}
|
||||||
part->~Partition();
|
part->~Flash_Access();
|
||||||
free(part);
|
free(part);
|
||||||
s_instances[handle].instance->~WL_Flash();
|
s_instances[handle].instance->~WL_Flash();
|
||||||
free(s_instances[handle].instance);
|
free(s_instances[handle].instance);
|
||||||
s_instances[handle].instance = NULL;
|
s_instances[handle].instance = NULL;
|
||||||
|
_lock_release(&s_instances[handle].lock);
|
||||||
_lock_close(&s_instances[handle].lock); // also zeroes the lock variable
|
_lock_close(&s_instances[handle].lock); // also zeroes the lock variable
|
||||||
}
|
}
|
||||||
|
|
||||||
_lock_release(&s_instances_lock);
|
_lock_release(&s_instances_lock);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t wl_erase_range(wl_handle_t handle, size_t start_addr, size_t size)
|
esp_err_t wl_erase_range(wl_handle_t handle, size_t start_addr, size_t size)
|
||||||
{
|
{
|
||||||
|
_lock_acquire(&s_instances_lock);
|
||||||
esp_err_t result = check_handle(handle, __func__);
|
esp_err_t result = check_handle(handle, __func__);
|
||||||
if (result != ESP_OK) {
|
if (result == ESP_OK) {
|
||||||
return result;
|
_lock_acquire(&s_instances[handle].lock);
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
|
result = s_instances[handle].instance->erase_range(start_addr, size);
|
||||||
|
_lock_release(&s_instances[handle].lock);
|
||||||
|
} else {
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
}
|
}
|
||||||
_lock_acquire(&s_instances[handle].lock);
|
|
||||||
result = s_instances[handle].instance->erase_range(start_addr, size);
|
|
||||||
_lock_release(&s_instances[handle].lock);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t wl_write(wl_handle_t handle, size_t dest_addr, const void *src, size_t size)
|
esp_err_t wl_write(wl_handle_t handle, size_t dest_addr, const void *src, size_t size)
|
||||||
{
|
{
|
||||||
|
_lock_acquire(&s_instances_lock);
|
||||||
esp_err_t result = check_handle(handle, __func__);
|
esp_err_t result = check_handle(handle, __func__);
|
||||||
if (result != ESP_OK) {
|
if (result == ESP_OK) {
|
||||||
return result;
|
_lock_acquire(&s_instances[handle].lock);
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
|
result = s_instances[handle].instance->write(dest_addr, src, size);
|
||||||
|
_lock_release(&s_instances[handle].lock);
|
||||||
|
} else {
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
}
|
}
|
||||||
_lock_acquire(&s_instances[handle].lock);
|
|
||||||
result = s_instances[handle].instance->write(dest_addr, src, size);
|
|
||||||
_lock_release(&s_instances[handle].lock);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t wl_read(wl_handle_t handle, size_t src_addr, void *dest, size_t size)
|
esp_err_t wl_read(wl_handle_t handle, size_t src_addr, void *dest, size_t size)
|
||||||
{
|
{
|
||||||
|
_lock_acquire(&s_instances_lock);
|
||||||
esp_err_t result = check_handle(handle, __func__);
|
esp_err_t result = check_handle(handle, __func__);
|
||||||
if (result != ESP_OK) {
|
if (result == ESP_OK) {
|
||||||
return result;
|
_lock_acquire(&s_instances[handle].lock);
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
|
result = s_instances[handle].instance->read(src_addr, dest, size);
|
||||||
|
_lock_release(&s_instances[handle].lock);
|
||||||
|
} else {
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
}
|
}
|
||||||
_lock_acquire(&s_instances[handle].lock);
|
|
||||||
result = s_instances[handle].instance->read(src_addr, dest, size);
|
|
||||||
_lock_release(&s_instances[handle].lock);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t wl_size(wl_handle_t handle)
|
size_t wl_size(wl_handle_t handle)
|
||||||
{
|
{
|
||||||
esp_err_t err = check_handle(handle, __func__);
|
_lock_acquire(&s_instances_lock);
|
||||||
if (err != ESP_OK) {
|
esp_err_t result = check_handle(handle, __func__);
|
||||||
return 0;
|
if (result == ESP_OK) {
|
||||||
|
_lock_acquire(&s_instances[handle].lock);
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
|
result = s_instances[handle].instance->get_flash_size();
|
||||||
|
_lock_release(&s_instances[handle].lock);
|
||||||
|
} else {
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
}
|
}
|
||||||
_lock_acquire(&s_instances[handle].lock);
|
|
||||||
size_t result = s_instances[handle].instance->get_flash_size();
|
|
||||||
_lock_release(&s_instances[handle].lock);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t wl_sector_size(wl_handle_t handle)
|
size_t wl_sector_size(wl_handle_t handle)
|
||||||
{
|
{
|
||||||
esp_err_t err = check_handle(handle, __func__);
|
_lock_acquire(&s_instances_lock);
|
||||||
if (err != ESP_OK) {
|
esp_err_t result = check_handle(handle, __func__);
|
||||||
return 0;
|
if (result == ESP_OK) {
|
||||||
|
_lock_acquire(&s_instances[handle].lock);
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
|
result = s_instances[handle].instance->get_sector_size();
|
||||||
|
_lock_release(&s_instances[handle].lock);
|
||||||
|
} else {
|
||||||
|
_lock_release(&s_instances_lock);
|
||||||
}
|
}
|
||||||
_lock_acquire(&s_instances[handle].lock);
|
|
||||||
size_t result = s_instances[handle].instance->get_sector_size();
|
|
||||||
_lock_release(&s_instances[handle].lock);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t check_handle(wl_handle_t handle, const char *func)
|
|
||||||
{
|
|
||||||
if (handle == WL_INVALID_HANDLE) {
|
|
||||||
ESP_LOGE(TAG, "%s: invalid handle", func);
|
|
||||||
return ESP_ERR_NOT_FOUND;
|
|
||||||
}
|
|
||||||
if (handle >= MAX_WL_HANDLES) {
|
|
||||||
ESP_LOGE(TAG, "%s: instance[0x%08" PRIx32 "] out of range", func, handle);
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
if (s_instances[handle].instance == NULL) {
|
|
||||||
ESP_LOGE(TAG, "%s: instance[0x%08" PRIx32 "] not initialized", func, handle);
|
|
||||||
return ESP_ERR_NOT_FOUND;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <new>
|
||||||
|
#include <sys/lock.h>
|
||||||
|
#include "wear_levelling.h"
|
||||||
|
#include "WL_Config.h"
|
||||||
|
#include "WL_Ext_Cfg.h"
|
||||||
|
#include "WL_Flash.h"
|
||||||
|
#include "WL_Ext_Perf.h"
|
||||||
|
#include "WL_Ext_Safe.h"
|
||||||
|
#include "BDL_Access.h"
|
||||||
|
|
||||||
|
#ifndef WL_DEFAULT_UPDATERATE
|
||||||
|
#define WL_DEFAULT_UPDATERATE 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WL_DEFAULT_TEMP_BUFF_SIZE
|
||||||
|
#define WL_DEFAULT_TEMP_BUFF_SIZE 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WL_DEFAULT_WRITE_SIZE
|
||||||
|
#define WL_DEFAULT_WRITE_SIZE 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WL_DEFAULT_START_ADDR
|
||||||
|
#define WL_DEFAULT_START_ADDR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WL_CURRENT_VERSION
|
||||||
|
#define WL_CURRENT_VERSION 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char *TAG = "wl_blockdev";
|
||||||
|
|
||||||
|
/* ========================================================================= */
|
||||||
|
/* BDL path – standalone WL instance, no wl_handle_t */
|
||||||
|
/* ========================================================================= */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
WL_Flash *wl_instance;
|
||||||
|
BDL_Access *bdl_access;
|
||||||
|
esp_blockdev_handle_t bottom_bdl;
|
||||||
|
_lock_t lock;
|
||||||
|
} wl_bdl_ctx_t;
|
||||||
|
|
||||||
|
static esp_err_t wl_bdl_read(esp_blockdev_handle_t dev, uint8_t *dst, size_t dst_size,
|
||||||
|
uint64_t src_addr, size_t len)
|
||||||
|
{
|
||||||
|
if (dst_size < len) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
if (src_addr % dev->geometry.read_size != 0 || len % dev->geometry.read_size) {
|
||||||
|
return ESP_ERR_INVALID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)dev->ctx;
|
||||||
|
_lock_acquire(&ctx->lock);
|
||||||
|
esp_err_t ret = ctx->wl_instance->read((size_t)src_addr, dst, len);
|
||||||
|
_lock_release(&ctx->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t wl_bdl_write(esp_blockdev_handle_t dev, const uint8_t *src,
|
||||||
|
uint64_t dst_addr, size_t len)
|
||||||
|
{
|
||||||
|
if (dst_addr % dev->geometry.write_size != 0 || len % dev->geometry.write_size) {
|
||||||
|
return ESP_ERR_INVALID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)dev->ctx;
|
||||||
|
_lock_acquire(&ctx->lock);
|
||||||
|
esp_err_t ret = ctx->wl_instance->write((size_t)dst_addr, src, len);
|
||||||
|
_lock_release(&ctx->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t wl_bdl_erase(esp_blockdev_handle_t dev, uint64_t start, size_t len)
|
||||||
|
{
|
||||||
|
if (start % dev->geometry.erase_size != 0 || len % dev->geometry.erase_size) {
|
||||||
|
return ESP_ERR_INVALID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)dev->ctx;
|
||||||
|
_lock_acquire(&ctx->lock);
|
||||||
|
esp_err_t ret = ctx->wl_instance->erase_range((size_t)start, len);
|
||||||
|
_lock_release(&ctx->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t wl_bdl_sync(esp_blockdev_handle_t dev)
|
||||||
|
{
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)dev->ctx;
|
||||||
|
_lock_acquire(&ctx->lock);
|
||||||
|
esp_err_t ret = ctx->wl_instance->flush();
|
||||||
|
_lock_release(&ctx->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t wl_bdl_ioctl(esp_blockdev_handle_t dev, const uint8_t cmd, void *args)
|
||||||
|
{
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)dev->ctx;
|
||||||
|
if (ctx->bottom_bdl == ESP_BLOCKDEV_HANDLE_INVALID) {
|
||||||
|
return ESP_ERR_NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
if (ctx->bottom_bdl->ops->ioctl == NULL) {
|
||||||
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
return ctx->bottom_bdl->ops->ioctl(ctx->bottom_bdl, cmd, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t wl_bdl_release(esp_blockdev_handle_t dev)
|
||||||
|
{
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)dev->ctx;
|
||||||
|
|
||||||
|
if (ctx->wl_instance) {
|
||||||
|
if (!ctx->bdl_access->is_readonly()) {
|
||||||
|
ctx->wl_instance->flush();
|
||||||
|
}
|
||||||
|
ctx->wl_instance->~WL_Flash();
|
||||||
|
free(ctx->wl_instance);
|
||||||
|
}
|
||||||
|
if (ctx->bdl_access) {
|
||||||
|
ctx->bdl_access->~BDL_Access();
|
||||||
|
free(ctx->bdl_access);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lock_close(&ctx->lock);
|
||||||
|
free(ctx);
|
||||||
|
free(dev);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const esp_blockdev_ops_t s_wl_bdl_ops = {
|
||||||
|
.read = wl_bdl_read,
|
||||||
|
.write = wl_bdl_write,
|
||||||
|
.erase = wl_bdl_erase,
|
||||||
|
.sync = wl_bdl_sync,
|
||||||
|
.ioctl = wl_bdl_ioctl,
|
||||||
|
.release = wl_bdl_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t wl_get_blockdev(const esp_blockdev_handle_t bdl_bottom_device,
|
||||||
|
esp_blockdev_handle_t *out_bdl_handle_ptr)
|
||||||
|
{
|
||||||
|
if (bdl_bottom_device == ESP_BLOCKDEV_HANDLE_INVALID || out_bdl_handle_ptr == NULL) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_bdl_handle_ptr = ESP_BLOCKDEV_HANDLE_INVALID;
|
||||||
|
|
||||||
|
if (bdl_bottom_device->geometry.disk_size > UINT32_MAX) {
|
||||||
|
ESP_LOGE(TAG, "%s: disk_size 0x%016" PRIx64 " exceeds uint32_t range, "
|
||||||
|
"WL configuration would be silently truncated",
|
||||||
|
__func__, bdl_bottom_device->geometry.disk_size);
|
||||||
|
return ESP_ERR_INVALID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t result = ESP_OK;
|
||||||
|
|
||||||
|
wl_bdl_ctx_t *ctx = (wl_bdl_ctx_t *)calloc(1, sizeof(wl_bdl_ctx_t));
|
||||||
|
if (!ctx) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
ctx->bottom_bdl = bdl_bottom_device;
|
||||||
|
|
||||||
|
void *access_mem = malloc(sizeof(BDL_Access));
|
||||||
|
if (!access_mem) {
|
||||||
|
free(ctx);
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
ctx->bdl_access = new (access_mem) BDL_Access(bdl_bottom_device);
|
||||||
|
|
||||||
|
void *wl_mem = NULL;
|
||||||
|
|
||||||
|
#if CONFIG_WL_SECTOR_SIZE == 512
|
||||||
|
#if CONFIG_WL_SECTOR_MODE == 1
|
||||||
|
wl_mem = malloc(sizeof(WL_Ext_Safe));
|
||||||
|
if (!wl_mem) { result = ESP_ERR_NO_MEM; goto fail; }
|
||||||
|
ctx->wl_instance = new (wl_mem) WL_Ext_Safe();
|
||||||
|
#else
|
||||||
|
wl_mem = malloc(sizeof(WL_Ext_Perf));
|
||||||
|
if (!wl_mem) { result = ESP_ERR_NO_MEM; goto fail; }
|
||||||
|
ctx->wl_instance = new (wl_mem) WL_Ext_Perf();
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#if CONFIG_WL_SECTOR_SIZE == 4096
|
||||||
|
wl_mem = malloc(sizeof(WL_Flash));
|
||||||
|
if (!wl_mem) { result = ESP_ERR_NO_MEM; goto fail; }
|
||||||
|
ctx->wl_instance = new (wl_mem) WL_Flash();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
wl_ext_cfg_t cfg;
|
||||||
|
cfg.wl_partition_start_addr = WL_DEFAULT_START_ADDR;
|
||||||
|
cfg.wl_partition_size = (uint32_t)bdl_bottom_device->geometry.disk_size;
|
||||||
|
cfg.wl_page_size = bdl_bottom_device->geometry.erase_size;
|
||||||
|
cfg.flash_sector_size = bdl_bottom_device->geometry.erase_size;
|
||||||
|
cfg.wl_update_rate = WL_DEFAULT_UPDATERATE;
|
||||||
|
cfg.wl_pos_update_record_size = WL_DEFAULT_WRITE_SIZE;
|
||||||
|
cfg.version = WL_CURRENT_VERSION;
|
||||||
|
cfg.wl_temp_buff_size = WL_DEFAULT_TEMP_BUFF_SIZE;
|
||||||
|
cfg.fat_sector_size = CONFIG_WL_SECTOR_SIZE;
|
||||||
|
|
||||||
|
result = ctx->wl_instance->config(&cfg, ctx->bdl_access);
|
||||||
|
if (result != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "%s: config failed, result=0x%x", __func__, result);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ctx->wl_instance->init();
|
||||||
|
if (result != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "%s: init failed, result=0x%x", __func__, result);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
esp_blockdev_t *dev = (esp_blockdev_t *)calloc(1, sizeof(esp_blockdev_t));
|
||||||
|
if (!dev) { result = ESP_ERR_NO_MEM; goto fail; }
|
||||||
|
|
||||||
|
dev->ctx = ctx;
|
||||||
|
dev->ops = &s_wl_bdl_ops;
|
||||||
|
dev->device_flags = bdl_bottom_device->device_flags;
|
||||||
|
dev->geometry.disk_size = ctx->wl_instance->get_flash_size();
|
||||||
|
dev->geometry.read_size = bdl_bottom_device->geometry.read_size;
|
||||||
|
dev->geometry.write_size = bdl_bottom_device->geometry.write_size;
|
||||||
|
dev->geometry.erase_size = ctx->wl_instance->get_sector_size();
|
||||||
|
dev->geometry.recommended_read_size = bdl_bottom_device->geometry.recommended_read_size;
|
||||||
|
dev->geometry.recommended_write_size = bdl_bottom_device->geometry.recommended_write_size;
|
||||||
|
dev->geometry.recommended_erase_size = ctx->wl_instance->get_sector_size();
|
||||||
|
|
||||||
|
_lock_init(&ctx->lock);
|
||||||
|
*out_bdl_handle_ptr = dev;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (ctx->wl_instance) { ctx->wl_instance->~WL_Flash(); free(ctx->wl_instance); }
|
||||||
|
if (ctx->bdl_access) { ctx->bdl_access->~BDL_Access(); free(ctx->bdl_access); }
|
||||||
|
free(ctx);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user