From 63a708fcaa15de5e34d6e9aacfbd4debde52378e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Thu, 12 Mar 2026 10:25:28 +0100 Subject: [PATCH] feat(storage/blockdev): Add memory mapping device driver --- components/esp_blockdev_util/CMakeLists.txt | 3 + .../include/esp_blockdev/memory.h | 51 +++++ components/esp_blockdev_util/memory.c | 174 ++++++++++++++++ .../test_apps/.build-test-rules.yml | 9 + .../test_apps/memory_blockdev/CMakeLists.txt | 11 + .../test_apps/memory_blockdev/README.md | 2 + .../memory_blockdev/main/CMakeLists.txt | 2 + .../main/test_memory_blockdev.c | 191 ++++++++++++++++++ .../memory_blockdev/pytest_memory_blockdev.py | 11 + .../memory_blockdev/sdkconfig.defaults | 1 + 10 files changed, 455 insertions(+) create mode 100644 components/esp_blockdev_util/CMakeLists.txt create mode 100644 components/esp_blockdev_util/include/esp_blockdev/memory.h create mode 100644 components/esp_blockdev_util/memory.c create mode 100644 components/esp_blockdev_util/test_apps/.build-test-rules.yml create mode 100644 components/esp_blockdev_util/test_apps/memory_blockdev/CMakeLists.txt create mode 100644 components/esp_blockdev_util/test_apps/memory_blockdev/README.md create mode 100644 components/esp_blockdev_util/test_apps/memory_blockdev/main/CMakeLists.txt create mode 100644 components/esp_blockdev_util/test_apps/memory_blockdev/main/test_memory_blockdev.c create mode 100644 components/esp_blockdev_util/test_apps/memory_blockdev/pytest_memory_blockdev.py create mode 100644 components/esp_blockdev_util/test_apps/memory_blockdev/sdkconfig.defaults diff --git a/components/esp_blockdev_util/CMakeLists.txt b/components/esp_blockdev_util/CMakeLists.txt new file mode 100644 index 0000000000..69b482aad3 --- /dev/null +++ b/components/esp_blockdev_util/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS "include" + PRIV_REQUIRES esp_blockdev) diff --git a/components/esp_blockdev_util/include/esp_blockdev/memory.h b/components/esp_blockdev_util/include/esp_blockdev/memory.h new file mode 100644 index 0000000000..d245e706b3 --- /dev/null +++ b/components/esp_blockdev_util/include/esp_blockdev/memory.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include + +#include "esp_blockdev.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a memory-backed block device that wraps an existing buffer + * + * @param buffer Pointer to the storage backing the device + * @param buffer_size Size of the storage buffer in bytes + * @param geometry Desired geometry for the device; geometry->disk_size must not exceed buffer_size + * @param read_only Set true to disallow write/erase operations on the mapped memory + * @param out Output pointer that receives the created block device handle; unchanged on failure + * + * @return ESP_ERR_INVALID_ARG Invalid argument provided + * @return ESP_ERR_INVALID_SIZE Provided buffer or geometry is incompatible + * @return ESP_ERR_NO_MEM Memory allocation for device descriptor failed + * @return ESP_OK on success + */ +esp_err_t esp_blockdev_memory_get_from_buffer(uint8_t *buffer, size_t buffer_size, const esp_blockdev_geometry_t *geometry, bool read_only, esp_blockdev_handle_t *out); + +/** + * @brief Create a memory-backed block device with internally allocated storage + * + * @param geometry Desired geometry for the device; geometry->disk_size determines the allocation size + * @param caps Heap capability flags passed to heap_caps_malloc + * @param out Output pointer that receives the created block device handle; unchanged on failure + * + * @return ESP_ERR_INVALID_ARG Invalid argument provided + * @return ESP_ERR_INVALID_SIZE Allocation size exceeds supported limits + * @return ESP_ERR_NO_MEM Memory allocation failed + * @return ESP_OK on success + */ +esp_err_t esp_blockdev_memory_create_with_heap_caps(const esp_blockdev_geometry_t *geometry, uint32_t caps, esp_blockdev_handle_t *out); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_blockdev_util/memory.c b/components/esp_blockdev_util/memory.c new file mode 100644 index 0000000000..0b27d55fda --- /dev/null +++ b/components/esp_blockdev_util/memory.c @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_heap_caps.h" + +#include "esp_blockdev.h" +#include "esp_blockdev/memory.h" + +static const char *TAG = "esp_blockdev/memory"; + +typedef struct { + esp_blockdev_t dev; + uint8_t *buffer; + size_t capacity; + bool owns_buffer; +} esp_blockdev_memory_t; + +static esp_err_t bd_memory_read(esp_blockdev_handle_t dev_handle, uint8_t *dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len) +{ + ESP_RETURN_ON_FALSE(dev_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "The dev_handle cannot be NULL"); + ESP_RETURN_ON_FALSE(dst_buf != NULL, ESP_ERR_INVALID_ARG, TAG, "The destination buffer cannot be NULL"); + ESP_RETURN_ON_FALSE(data_read_len <= dst_buf_size, ESP_ERR_INVALID_SIZE, TAG, "Destination buffer too small"); + ESP_RETURN_ON_FALSE(src_addr + data_read_len <= dev_handle->geometry.disk_size, ESP_ERR_INVALID_ARG, TAG, "The address range falls outside of the disk"); + + esp_blockdev_memory_t *dev = (esp_blockdev_memory_t *)dev_handle; + size_t offset = (size_t)src_addr; + + memcpy(dst_buf, dev->buffer + offset, data_read_len); + + return ESP_OK; +} + +static esp_err_t bd_memory_write(esp_blockdev_handle_t dev_handle, const uint8_t* src_buf, uint64_t dst_addr, size_t data_write_len) +{ + ESP_RETURN_ON_FALSE(dev_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "The dev_handle cannot be NULL"); + ESP_RETURN_ON_FALSE(src_buf != NULL, ESP_ERR_INVALID_ARG, TAG, "The source buffer cannot be NULL"); + ESP_RETURN_ON_FALSE(!dev_handle->device_flags.read_only, ESP_ERR_INVALID_STATE, TAG, "The device is read-only"); + ESP_RETURN_ON_FALSE(dst_addr + data_write_len <= dev_handle->geometry.disk_size, ESP_ERR_INVALID_ARG, TAG, "The address range falls outside of the disk"); + + esp_blockdev_memory_t *dev = (esp_blockdev_memory_t *)dev_handle; + + size_t offset = (size_t)dst_addr; + + memcpy(dev->buffer + offset, src_buf, data_write_len); + + return ESP_OK; +} + +static esp_err_t bd_memory_erase(esp_blockdev_handle_t dev_handle, uint64_t start_addr, size_t erase_len) +{ + ESP_RETURN_ON_FALSE(dev_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "The dev_handle cannot be NULL"); + ESP_RETURN_ON_FALSE(!dev_handle->device_flags.read_only, ESP_ERR_INVALID_STATE, TAG, "The device is read-only"); + ESP_RETURN_ON_FALSE(start_addr + erase_len <= dev_handle->geometry.disk_size, ESP_ERR_INVALID_ARG, TAG, "The address range falls outside of the disk"); + + esp_blockdev_memory_t *dev = (esp_blockdev_memory_t *)dev_handle; + + size_t offset = (size_t)start_addr; + uint8_t erase_value = dev_handle->device_flags.default_val_after_erase ? 0xFF : 0; + + memset(dev->buffer + offset, erase_value, erase_len); + + return ESP_OK; +} + +static esp_err_t bd_memory_sync(esp_blockdev_handle_t dev_handle) +{ + ESP_RETURN_ON_FALSE(dev_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "The dev_handle cannot be NULL"); + + return ESP_OK; +} + +static esp_err_t bd_memory_ioctl(esp_blockdev_handle_t dev_handle, const uint8_t cmd, void *args) +{ + ESP_RETURN_ON_FALSE(dev_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "The dev_handle cannot be NULL"); + (void)cmd; + (void)args; + + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t bd_memory_release(esp_blockdev_handle_t dev_handle) +{ + ESP_RETURN_ON_FALSE(dev_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "The dev_handle cannot be NULL"); + + esp_blockdev_memory_t *dev = (esp_blockdev_memory_t *)dev_handle; + + if (dev->owns_buffer && dev->buffer != NULL) { + heap_caps_free(dev->buffer); + } + + free(dev); + + return ESP_OK; +} + +static const esp_blockdev_ops_t g_memory_blockdev_ops = { + .read = bd_memory_read, + .write = bd_memory_write, + .erase = bd_memory_erase, + .sync = bd_memory_sync, + .ioctl = bd_memory_ioctl, + .release = bd_memory_release, +}; + +static esp_err_t esp_blockdev_memory_create_internal(uint8_t *buffer, size_t buffer_size, const esp_blockdev_geometry_t *geometry, bool owns_buffer, bool read_only, esp_blockdev_handle_t *out) +{ + ESP_RETURN_ON_FALSE(out != NULL, ESP_ERR_INVALID_ARG, TAG, "The out pointer cannot be NULL"); + *out = ESP_BLOCKDEV_HANDLE_INVALID; + ESP_RETURN_ON_FALSE(buffer != NULL, ESP_ERR_INVALID_ARG, TAG, "The buffer cannot be NULL"); + ESP_RETURN_ON_FALSE(geometry != NULL, ESP_ERR_INVALID_ARG, TAG, "Geometry pointer cannot be NULL"); + ESP_RETURN_ON_FALSE(geometry->disk_size > 0, ESP_ERR_INVALID_ARG, TAG, "Disk size must be greater than zero"); + ESP_RETURN_ON_FALSE(geometry->disk_size <= SIZE_MAX, ESP_ERR_INVALID_SIZE, TAG, "Disk size exceeds addressable memory span"); + ESP_RETURN_ON_FALSE(geometry->disk_size <= buffer_size, ESP_ERR_INVALID_SIZE, TAG, "Buffer smaller than requested disk size"); + + esp_blockdev_memory_t *dev = calloc(1, sizeof(esp_blockdev_memory_t)); + ESP_RETURN_ON_FALSE(dev != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate device structure"); + + *dev = (esp_blockdev_memory_t) { + .dev = { + .ctx = NULL, + .device_flags = { + .read_only = read_only, + .encrypted = false, + .erase_before_write = 0, + .and_type_write = 0, + .default_val_after_erase = false, + }, + .geometry = *geometry, + .ops = &g_memory_blockdev_ops, + }, + .buffer = buffer, + .capacity = buffer_size, + .owns_buffer = owns_buffer, + }; + + *out = (esp_blockdev_handle_t)dev; + + return ESP_OK; +} + +esp_err_t esp_blockdev_memory_get_from_buffer(uint8_t *buffer, size_t buffer_size, const esp_blockdev_geometry_t *geometry, bool read_only, esp_blockdev_handle_t *out) +{ + return esp_blockdev_memory_create_internal(buffer, buffer_size, geometry, false, read_only, out); +} + +esp_err_t esp_blockdev_memory_create_with_heap_caps(const esp_blockdev_geometry_t *geometry, uint32_t caps, esp_blockdev_handle_t *out) +{ + ESP_RETURN_ON_FALSE(geometry != NULL, ESP_ERR_INVALID_ARG, TAG, "Geometry pointer cannot be NULL"); + + size_t alloc_size = (size_t)geometry->disk_size; + uint8_t *buffer = heap_caps_malloc(alloc_size, caps); + ESP_RETURN_ON_FALSE(buffer != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate device buffer"); + + memset(buffer, 0x00, alloc_size); + + esp_err_t err = esp_blockdev_memory_create_internal(buffer, alloc_size, geometry, true, false, out); + if (err != ESP_OK) { + heap_caps_free(buffer); + return err; + } + + return ESP_OK; +} diff --git a/components/esp_blockdev_util/test_apps/.build-test-rules.yml b/components/esp_blockdev_util/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..0e99fb4bbc --- /dev/null +++ b/components/esp_blockdev_util/test_apps/.build-test-rules.yml @@ -0,0 +1,9 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/esp_blockdev_util/test_apps/memory_blockdev: + enable: + - if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux" + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3", "linux"] + temporary: true + reason: cover Xtensa and RISC-V targets diff --git a/components/esp_blockdev_util/test_apps/memory_blockdev/CMakeLists.txt b/components/esp_blockdev_util/test_apps/memory_blockdev/CMakeLists.txt new file mode 100644 index 0000000000..784cc149db --- /dev/null +++ b/components/esp_blockdev_util/test_apps/memory_blockdev/CMakeLists.txt @@ -0,0 +1,11 @@ +# This is the project CMakeLists.txt file for the memory blockdev test application +cmake_minimum_required(VERSION 3.22) + +set(EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/test_apps/components" + "${CMAKE_CURRENT_LIST_DIR}/../../" + "${CMAKE_CURRENT_LIST_DIR}/../../../esp_blockdev") + +set(COMPONENTS main) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(memory_blockdev_test) diff --git a/components/esp_blockdev_util/test_apps/memory_blockdev/README.md b/components/esp_blockdev_util/test_apps/memory_blockdev/README.md new file mode 100644 index 0000000000..691f9018de --- /dev/null +++ b/components/esp_blockdev_util/test_apps/memory_blockdev/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | Linux | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | ----- | diff --git a/components/esp_blockdev_util/test_apps/memory_blockdev/main/CMakeLists.txt b/components/esp_blockdev_util/test_apps/memory_blockdev/main/CMakeLists.txt new file mode 100644 index 0000000000..025a79a61f --- /dev/null +++ b/components/esp_blockdev_util/test_apps/memory_blockdev/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "test_memory_blockdev.c" + PRIV_REQUIRES unity esp_blockdev esp_blockdev_util) diff --git a/components/esp_blockdev_util/test_apps/memory_blockdev/main/test_memory_blockdev.c b/components/esp_blockdev_util/test_apps/memory_blockdev/main/test_memory_blockdev.c new file mode 100644 index 0000000000..5ed8849fab --- /dev/null +++ b/components/esp_blockdev_util/test_apps/memory_blockdev/main/test_memory_blockdev.c @@ -0,0 +1,191 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "unity.h" +#include "unity_test_utils.h" + +#include "esp_blockdev.h" +#include "esp_blockdev/memory.h" +#include "esp_heap_caps.h" + +TEST_CASE("memory blockdev basic operations", "[memory_blockdev]") +{ + uint8_t backing[128]; + memset(backing, 0x5A, sizeof(backing)); + + const esp_blockdev_geometry_t geometry = { + .disk_size = sizeof(backing), + .read_size = 1, + .write_size = 1, + .erase_size = 1, + .recommended_write_size = 16, + .recommended_read_size = 16, + .recommended_erase_size = 32, + }; + + esp_blockdev_handle_t dev = NULL; + TEST_ESP_OK(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing), &geometry, false, &dev)); + TEST_ASSERT_NOT_NULL(dev); + + TEST_ASSERT_EQUAL_UINT64(geometry.disk_size, dev->geometry.disk_size); + TEST_ASSERT_EQUAL_UINT32(geometry.read_size, dev->geometry.read_size); + TEST_ASSERT_EQUAL_UINT32(geometry.write_size, dev->geometry.write_size); + TEST_ASSERT_EQUAL_UINT32(geometry.erase_size, dev->geometry.erase_size); + TEST_ASSERT_FALSE(dev->device_flags.read_only); + TEST_ASSERT_FALSE(dev->device_flags.default_val_after_erase); + + uint8_t write_data[32]; + for (size_t i = 0; i < sizeof(write_data); ++i) { + write_data[i] = (uint8_t)(i + 1); + } + + TEST_ESP_OK(dev->ops->write(dev, write_data, 48, sizeof(write_data))); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_data, backing + 48, sizeof(write_data)); + + uint8_t read_buf[sizeof(write_data)]; + memset(read_buf, 0, sizeof(read_buf)); + TEST_ESP_OK(dev->ops->read(dev, read_buf, sizeof(read_buf), 48, sizeof(read_buf))); + TEST_ASSERT_EQUAL_UINT8_ARRAY(write_data, read_buf, sizeof(write_data)); + + TEST_ESP_OK(dev->ops->erase(dev, 48, sizeof(write_data))); + TEST_ASSERT_EACH_EQUAL_UINT8(0x00, backing + 48, sizeof(write_data)); + + TEST_ESP_OK(dev->ops->sync(dev)); + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, dev->ops->ioctl(dev, ESP_BLOCKDEV_CMD_USER_BASE, NULL)); + + TEST_ESP_OK(dev->ops->release(dev)); +} + +TEST_CASE("memory blockdev enforces read-only mappings", "[memory_blockdev]") +{ + uint8_t backing[64]; + for (size_t i = 0; i < sizeof(backing); ++i) { + backing[i] = (uint8_t)(i ^ 0xAA); + } + + const esp_blockdev_geometry_t ro_geometry = { + .disk_size = sizeof(backing), + .read_size = 1, + .write_size = 0, + .erase_size = 0, + .recommended_write_size = 0, + .recommended_read_size = 8, + .recommended_erase_size = 0, + }; + + esp_blockdev_handle_t dev = NULL; + TEST_ESP_OK(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing), &ro_geometry, true, &dev)); + TEST_ASSERT_NOT_NULL(dev); + TEST_ASSERT_TRUE(dev->device_flags.read_only); + + uint8_t read_buf[16]; + TEST_ESP_OK(dev->ops->read(dev, read_buf, sizeof(read_buf), 0, sizeof(read_buf))); + TEST_ASSERT_EQUAL_UINT8_ARRAY(backing, read_buf, sizeof(read_buf)); + + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, dev->ops->write(dev, read_buf, 0, sizeof(read_buf))); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, dev->ops->erase(dev, 0, sizeof(read_buf))); + + TEST_ESP_OK(dev->ops->release(dev)); + + /* Release must not take ownership of external buffers */ + const uint8_t expected_prefix[] = {0xAA, 0xAB, 0xA8, 0xA9}; + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_prefix, backing, sizeof(expected_prefix)); +} + +TEST_CASE("memory blockdev validates arguments", "[memory_blockdev]") +{ + uint8_t backing[32]; + memset(backing, 0x11, sizeof(backing)); + + esp_blockdev_geometry_t geometry = { + .disk_size = sizeof(backing), + .read_size = 1, + .write_size = 1, + .erase_size = 1, + .recommended_write_size = 0, + .recommended_read_size = 0, + .recommended_erase_size = 0, + }; + + esp_blockdev_handle_t dev = (esp_blockdev_handle_t)0xDEADBEEF; + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_blockdev_memory_get_from_buffer(NULL, sizeof(backing), &geometry, false, &dev)); + TEST_ASSERT_EQUAL_PTR(NULL, dev); + + dev = (esp_blockdev_handle_t)0xDEADBEEF; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_blockdev_memory_get_from_buffer(backing, sizeof(backing), NULL, false, &dev)); + TEST_ASSERT_EQUAL_PTR(NULL, dev); + + geometry.disk_size = sizeof(backing) + 1; + dev = (esp_blockdev_handle_t)0xDEADBEEF; + TEST_ESP_ERR(ESP_ERR_INVALID_SIZE, esp_blockdev_memory_get_from_buffer(backing, sizeof(backing), &geometry, false, &dev)); + TEST_ASSERT_EQUAL_PTR(NULL, dev); + + geometry.disk_size = 0; + dev = (esp_blockdev_handle_t)0xDEADBEEF; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_blockdev_memory_get_from_buffer(backing, sizeof(backing), &geometry, false, &dev)); + TEST_ASSERT_EQUAL_PTR(NULL, dev); + + geometry.disk_size = sizeof(backing); + esp_blockdev_handle_t heap_dev = NULL; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_blockdev_memory_create_with_heap_caps(NULL, MALLOC_CAP_8BIT, &heap_dev)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_blockdev_memory_create_with_heap_caps(&geometry, MALLOC_CAP_8BIT, NULL)); + + /* Successful create for subsequent bounds checks */ + TEST_ESP_OK(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing), &geometry, false, &dev)); + TEST_ASSERT_NOT_NULL(dev); + + uint8_t read_buf[16]; + TEST_ESP_ERR(ESP_ERR_INVALID_SIZE, dev->ops->read(dev, read_buf, sizeof(read_buf) - 1, 0, sizeof(read_buf))); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, dev->ops->read(dev, read_buf, sizeof(read_buf), sizeof(backing) - 4, sizeof(read_buf))); + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, dev->ops->write(dev, read_buf, sizeof(backing) - 8, sizeof(read_buf) + 1)); + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, dev->ops->erase(dev, sizeof(backing) - 4, sizeof(read_buf))); + + TEST_ESP_OK(dev->ops->release(dev)); +} + +TEST_CASE("memory blockdev heap allocation creates zeroed storage", "[memory_blockdev]") +{ + const esp_blockdev_geometry_t geometry = { + .disk_size = 64, + .read_size = 1, + .write_size = 1, + .erase_size = 1, + .recommended_write_size = 0, + .recommended_read_size = 0, + .recommended_erase_size = 0, + }; + + esp_blockdev_handle_t dev = NULL; + TEST_ESP_OK(esp_blockdev_memory_create_with_heap_caps(&geometry, MALLOC_CAP_8BIT, &dev)); + TEST_ASSERT_NOT_NULL(dev); + TEST_ASSERT_FALSE(dev->device_flags.read_only); + TEST_ASSERT_FALSE(dev->device_flags.default_val_after_erase); + + uint8_t read_buf[64]; + TEST_ESP_OK(dev->ops->read(dev, read_buf, sizeof(read_buf), 0, sizeof(read_buf))); + TEST_ASSERT_EACH_EQUAL_UINT8(0x00, read_buf, sizeof(read_buf)); + + uint8_t pattern[8]; + memset(pattern, 0xCD, sizeof(pattern)); + + TEST_ESP_OK(dev->ops->write(dev, pattern, 4, sizeof(pattern))); + TEST_ESP_OK(dev->ops->read(dev, read_buf, sizeof(pattern), 4, sizeof(pattern))); + TEST_ASSERT_EQUAL_UINT8_ARRAY(pattern, read_buf, sizeof(pattern)); + + TEST_ESP_OK(dev->ops->release(dev)); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/esp_blockdev_util/test_apps/memory_blockdev/pytest_memory_blockdev.py b/components/esp_blockdev_util/test_apps/memory_blockdev/pytest_memory_blockdev.py new file mode 100644 index 0000000000..c85ef1298c --- /dev/null +++ b/components/esp_blockdev_util/test_apps/memory_blockdev/pytest_memory_blockdev.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32', 'esp32c3', 'linux'], indirect=['target']) +def test_blockdev_memory_device(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_blockdev_util/test_apps/memory_blockdev/sdkconfig.defaults b/components/esp_blockdev_util/test_apps/memory_blockdev/sdkconfig.defaults new file mode 100644 index 0000000000..ae3f2121da --- /dev/null +++ b/components/esp_blockdev_util/test_apps/memory_blockdev/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_UNITY_ENABLE_64BIT=y