feat(wear_levelling): Added BDL support

This commit is contained in:
Martin Vychodil
2025-12-16 21:52:13 +01:00
parent 258d3fc830
commit 0a2fc7f34a
19 changed files with 940 additions and 106 deletions
+63
View File
@@ -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()
{
}
+3 -1
View File
@@ -1,13 +1,15 @@
idf_component_register(SRCS "Partition.cpp"
"BDL_Access.cpp"
"SPI_Flash.cpp"
"WL_Ext_Perf.cpp"
"WL_Ext_Safe.cpp"
"WL_Flash.cpp"
"crc32.cpp"
"wear_levelling.cpp"
"wl_blockdev.cpp"
INCLUDE_DIRS include
PRIV_INCLUDE_DIRS private_include
REQUIRES esp_partition
REQUIRES esp_partition esp_blockdev
PRIV_REQUIRES spi_flash)
if(CONFIG_COMPILER_STATIC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # TODO IDF-10089
+9 -19
View File
@@ -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
*/
#include <inttypes.h>
#include "esp_log.h"
#include "Partition.h"
#include <inttypes.h>
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 result = ESP_OK;
result = erase_range(sector * this->partition->erase_size, this->partition->erase_size);
return result;
return erase_range(sector * this->partition->erase_size, this->partition->erase_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);
if (result == ESP_OK) {
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_LOGV(TAG, "%s - start_address=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)start_address, (uint32_t)size);
return esp_partition_erase_range(this->partition, start_address, size);
}
esp_err_t Partition::write(size_t dest_addr, const void *src, size_t size)
{
esp_err_t result = ESP_OK;
result = esp_partition_write(this->partition, dest_addr, src, size);
return result;
ESP_LOGV(TAG, "%s - dest_addr=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)dest_addr, (uint32_t)size);
return esp_partition_write(this->partition, dest_addr, src, size);
}
esp_err_t Partition::read(size_t src_addr, void *dest, size_t size)
{
esp_err_t result = ESP_OK;
result = esp_partition_read(this->partition, src_addr, dest, size);
return result;
ESP_LOGV(TAG, "%s - src_addr=0x%08" PRIx32 ", size=0x%08" PRIx32, __func__, (uint32_t)src_addr, (uint32_t)size);
return esp_partition_read(this->partition, src_addr, dest, size);
}
size_t Partition::get_sector_size()
@@ -64,5 +55,4 @@ bool Partition::is_readonly()
Partition::~Partition()
{
}
+1 -2
View File
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include "WL_Ext_Perf.h"
#include "Partition.h"
#include <stdlib.h>
#include <inttypes.h>
#include "esp_log.h"
@@ -27,7 +26,7 @@ WL_Ext_Perf::~WL_Ext_Perf()
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;
+1 -1
View File
@@ -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;
+3 -4
View File
@@ -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
*/
#include <stdio.h>
#include "esp_random.h"
#include "esp_log.h"
#include "Partition.h"
#include "WL_Flash.h"
#include <stdlib.h>
#include "crc32.h"
@@ -39,7 +38,7 @@ WL_Flash::~WL_Flash()
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__,
(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;
}
Partition *WL_Flash::get_part()
Flash_Access *WL_Flash::get_part()
{
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
*/
@@ -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)
{
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);
}
@@ -421,3 +421,233 @@ TEST_CASE("power down between WL status 1 and WL status 2 update", "[wear_levell
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
@idf_parametrize('target', ['linux'], indirect=['target'])
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
*/
@@ -9,6 +9,7 @@
#include "esp_log.h"
#include "esp_partition.h"
#include "esp_blockdev.h"
#ifdef __cplusplus
extern "C" {
@@ -120,6 +121,39 @@ size_t wl_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
} // 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
*/
@@ -30,6 +30,11 @@ public:
return ESP_OK;
};
virtual bool is_readonly()
{
return false;
};
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
*/
@@ -22,21 +22,21 @@ class Partition : public Flash_Access
public:
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);
virtual esp_err_t erase_range(size_t start_address, size_t size);
esp_err_t erase_sector(size_t sector) override;
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);
virtual esp_err_t read(size_t src_addr, void *dest, size_t size);
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;
virtual size_t get_sector_size();
virtual bool is_readonly();
size_t get_sector_size() override;
bool is_readonly() override;
~Partition() override;
virtual ~Partition();
protected:
const esp_partition_t *partition;
};
#endif // _Partition_H_
@@ -6,7 +6,6 @@
#ifndef _WL_Ext_Perf_H_
#define _WL_Ext_Perf_H_
#include "Partition.h"
#include "WL_Flash.h"
#include "WL_Ext_Cfg.h"
@@ -16,7 +15,7 @@ public:
WL_Ext_Perf();
~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;
size_t get_flash_size() override;
@@ -6,7 +6,6 @@
#ifndef _WL_Ext_Safe_H_
#define _WL_Ext_Safe_H_
#include "Partition.h"
#include "WL_Flash.h"
#include "WL_Ext_Cfg.h"
#include "WL_Ext_Perf.h"
@@ -17,7 +16,7 @@ public:
WL_Ext_Safe();
~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;
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
*/
@@ -8,7 +8,6 @@
#include "esp_err.h"
#include "Flash_Access.h"
#include "Partition.h"
#include "WL_Config.h"
#include "WL_State.h"
@@ -22,7 +21,7 @@ public :
WL_Flash();
~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();
size_t get_flash_size() override;
@@ -36,7 +35,7 @@ public :
esp_err_t flush() override;
Partition *get_part();
Flash_Access *get_part();
wl_config_t *get_cfg();
protected:
@@ -44,7 +43,7 @@ protected:
bool initialized = false;
wl_state_t state;
wl_config_t cfg;
Partition *partition = NULL;
Flash_Access *partition = NULL;
size_t addr_cfg;
size_t addr_state1;
@@ -1,5 +1,6 @@
idf_component_register(SRCS test_wl.c
PRIV_INCLUDE_DIRS .
PRIV_REQUIRES wear_levelling unity
PRIV_REQUIRES unity
REQUIRES wear_levelling
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
*/
@@ -319,12 +319,214 @@ TEST(wear_levelling, version_update)
}
#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)
{
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, multiple_tasks_single_handle)
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
RUN_TEST_CASE(wear_levelling, version_update)
+66 -45
View File
@@ -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
*/
#include <stdlib.h>
#include <stdint.h>
#include <new>
#include <sys/lock.h>
#include "wear_levelling.h"
@@ -13,7 +14,6 @@
#include "WL_Flash.h"
#include "WL_Ext_Perf.h"
#include "WL_Ext_Safe.h"
#include "SPI_Flash.h"
#include "Partition.h"
#ifndef MAX_WL_HANDLES
@@ -49,7 +49,22 @@ static wl_instance_t s_instances[MAX_WL_HANDLES];
static _lock_t s_instances_lock;
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)
{
@@ -170,100 +185,106 @@ out:
esp_err_t wl_unmount(wl_handle_t handle)
{
esp_err_t result = ESP_OK;
_lock_acquire(&s_instances_lock);
result = check_handle(handle, __func__);
esp_err_t result = check_handle(handle, __func__);
if (result == ESP_OK) {
// 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
// Acquire per-instance lock to drain any in-flight I/O that is
// running outside the global lock.
_lock_acquire(&s_instances[handle].lock);
Flash_Access *part = s_instances[handle].instance->get_part();
if (!part->is_readonly()) {
result = s_instances[handle].instance->flush();
}
part->~Partition();
part->~Flash_Access();
free(part);
s_instances[handle].instance->~WL_Flash();
free(s_instances[handle].instance);
s_instances[handle].instance = NULL;
_lock_release(&s_instances[handle].lock);
_lock_close(&s_instances[handle].lock); // also zeroes the lock variable
}
_lock_release(&s_instances_lock);
return result;
}
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__);
if (result != ESP_OK) {
return result;
}
if (result == ESP_OK) {
_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);
}
return result;
}
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__);
if (result != ESP_OK) {
return result;
}
if (result == ESP_OK) {
_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);
}
return result;
}
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__);
if (result != ESP_OK) {
return result;
}
if (result == ESP_OK) {
_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);
}
return result;
}
size_t wl_size(wl_handle_t handle)
{
esp_err_t err = check_handle(handle, __func__);
if (err != ESP_OK) {
return 0;
}
_lock_acquire(&s_instances_lock);
esp_err_t result = check_handle(handle, __func__);
if (result == ESP_OK) {
_lock_acquire(&s_instances[handle].lock);
size_t result = s_instances[handle].instance->get_flash_size();
_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);
}
return result;
}
size_t wl_sector_size(wl_handle_t handle)
{
esp_err_t err = check_handle(handle, __func__);
if (err != ESP_OK) {
return 0;
}
_lock_acquire(&s_instances_lock);
esp_err_t result = check_handle(handle, __func__);
if (result == ESP_OK) {
_lock_acquire(&s_instances[handle].lock);
size_t result = s_instances[handle].instance->get_sector_size();
_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);
}
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;
}
+249
View File
@@ -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;
}