feat(storage): Add support for SDMMC BDL

This commit is contained in:
Adam Múdry
2025-06-03 17:56:07 +02:00
parent 03cc50cf65
commit 3ba496c6b9
11 changed files with 312 additions and 1 deletions
@@ -22,5 +22,6 @@ set(priv_requires "sdmmc"
)
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
PRIV_REQUIRES ${priv_requires}
WHOLE_ARCHIVE TRUE)
+2
View File
@@ -9,6 +9,7 @@ set(srcs "sdmmc_cmd.c"
"sdmmc_init.c"
"sdmmc_mmc.c"
"sdmmc_sd.c"
"sdmmc_blockdev.c"
"sd_pwr_ctrl/sd_pwr_ctrl.c")
if(CONFIG_SD_ENABLE_SDIO_SUPPORT)
@@ -21,4 +22,5 @@ endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS include
REQUIRES esp_blockdev
PRIV_REQUIRES soc esp_timer esp_mm)
+16 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,6 +9,7 @@
#include <stdio.h>
#include "esp_err.h"
#include "sd_protocol_types.h"
#include "esp_blockdev.h"
#ifdef __cplusplus
extern "C" {
@@ -367,6 +368,20 @@ esp_err_t sdmmc_io_get_cis_data(sdmmc_card_t* card, uint8_t* out_buffer, size_t
*/
esp_err_t sdmmc_io_print_cis_info(uint8_t* buffer, size_t buffer_size, FILE* fp);
/**
* Get a block device handle for the SD/MMC card
*
* This function allocates a block device handle and initializes it with the
* card information.
*
* @param[in] card Pointer to card information structure previously initialized using sdmmc_card_init.
* @param[in,out] out_handle Pointer to variable which will receive the block device handle.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if card or out_handle is NULL
* - ESP_ERR_NO_MEM if memory allocation fails
*/
esp_err_t sdmmc_get_blockdev(sdmmc_card_t* card, esp_blockdev_handle_t* out_handle);
#ifdef __cplusplus
}
+144
View File
@@ -0,0 +1,144 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include "esp_private/sdmmc_common.h"
#include "esp_blockdev.h"
#include "sdmmc_cmd.h"
static esp_err_t calculate_start_sector_num_and_sector_count(size_t sector_size, uint64_t addr, size_t data_len, size_t* out_start_sector_num, size_t* out_num_of_sectors)
{
size_t offset_in_start_sector = (size_t) addr % sector_size;
size_t offset_in_end_sector = (size_t) data_len % sector_size;
// Has to be aligned to sector boundaries
if (offset_in_start_sector != 0 || offset_in_end_sector != 0) {
return ESP_ERR_INVALID_ARG;
}
size_t start_sector_num = (size_t) addr / sector_size;
size_t last_byte_addr = (size_t) (addr + data_len - 1); // Address of the last accessed byte
size_t end_sector_num = last_byte_addr / sector_size;
if (out_start_sector_num) {
*out_start_sector_num = start_sector_num;
}
if (out_num_of_sectors) {
*out_num_of_sectors = end_sector_num - start_sector_num + 1;
}
return ESP_OK;
}
static esp_err_t sdmmc_blockdev_read(esp_blockdev_handle_t handle, uint8_t* dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len)
{
if (handle == NULL || dst_buf == NULL || dst_buf_size < data_read_len) {
return ESP_ERR_INVALID_ARG;
}
sdmmc_card_t* card = (sdmmc_card_t*) handle->ctx;
size_t start_sector_num, num_of_sectors;
esp_err_t err = calculate_start_sector_num_and_sector_count((size_t) card->csd.sector_size, src_addr, data_read_len, &start_sector_num, &num_of_sectors);
if (err != ESP_OK) {
return err;
}
return sdmmc_read_sectors(card, (void*) dst_buf, start_sector_num, num_of_sectors);
}
static esp_err_t sdmmc_blockdev_write(esp_blockdev_handle_t handle, const uint8_t* src_buf, uint64_t dst_addr, size_t data_write_len)
{
if (handle == NULL || src_buf == NULL) {
return ESP_ERR_INVALID_ARG;
}
sdmmc_card_t* card = (sdmmc_card_t*) handle->ctx;
size_t start_sector_num, num_of_sectors;
esp_err_t err = calculate_start_sector_num_and_sector_count((size_t) card->csd.sector_size, dst_addr, data_write_len, &start_sector_num, &num_of_sectors);
if (err != ESP_OK) {
return err;
}
return sdmmc_write_sectors(card, (const void*) src_buf, start_sector_num, num_of_sectors);
}
static esp_err_t sdmmc_blockdev_erase(esp_blockdev_handle_t handle, uint64_t start_addr, size_t erase_len)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
sdmmc_card_t* card = (sdmmc_card_t*) handle->ctx;
size_t start_sector_num, num_of_sectors;
esp_err_t err = calculate_start_sector_num_and_sector_count((size_t) card->csd.sector_size, start_addr, erase_len, &start_sector_num, &num_of_sectors);
if (err != ESP_OK) {
return err;
}
sdmmc_erase_arg_t arg = sdmmc_can_discard(card) == ESP_OK ? SDMMC_DISCARD_ARG : SDMMC_ERASE_ARG;
return sdmmc_erase_sectors(card, start_sector_num, num_of_sectors, arg);
}
static esp_err_t sdmmc_blockdev_ioctl(esp_blockdev_handle_t handle, const uint8_t cmd, void* args)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
(void) cmd;
(void) args;
return ESP_ERR_NOT_SUPPORTED;
}
static inline esp_err_t sdmmc_blockdev_sync_noop(esp_blockdev_handle_t handle)
{
(void) handle;
return ESP_OK; // NOOP, write operations are always synchronous
}
static esp_err_t sdmmc_release_blockdev(esp_blockdev_handle_t handle)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
free(handle);
return ESP_OK;
}
static const esp_blockdev_ops_t sdmmc_blockdev_ops = {
.read = sdmmc_blockdev_read,
.write = sdmmc_blockdev_write,
.erase = sdmmc_blockdev_erase,
.ioctl = sdmmc_blockdev_ioctl,
.sync = sdmmc_blockdev_sync_noop,
.release = sdmmc_release_blockdev,
};
esp_err_t sdmmc_get_blockdev(sdmmc_card_t* card, esp_blockdev_handle_t* out_handle)
{
if (card == NULL || out_handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_blockdev_handle_t out = (esp_blockdev_handle_t) calloc(1, sizeof(esp_blockdev_t));
if (out == NULL) {
return ESP_ERR_NO_MEM;
}
out->ctx = (void*) card;
out->device_flags.default_val_after_erase = card->scr.erase_mem_state;
out->geometry.disk_size = (uint64_t) (card->csd.capacity * card->csd.sector_size);
out->geometry.read_size = (size_t) card->csd.sector_size;
out->geometry.write_size = (size_t) card->csd.sector_size;
out->geometry.erase_size = (size_t) card->csd.sector_size;
out->ops = &sdmmc_blockdev_ops;
*out_handle = out;
return ESP_OK;
}
@@ -0,0 +1,13 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/sdmmc/test_apps:
disable:
- if: SOC_SDMMC_HOST_SUPPORTED != 1
disable_test:
- if: IDF_TARGET not in ["esp32"]
temporary: false
reason: only one target required for running the test
depends_components:
- sdmmc
- esp_driver_sdmmc
- vfs
+10
View File
@@ -0,0 +1,10 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.22)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/components/esp_driver_sdmmc/test_apps/sd_test_utils/components")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/components/esp_driver_sdmmc/test_apps/sdmmc/components")
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(sdmmc_test)
+28
View File
@@ -0,0 +1,28 @@
| Supported Targets | ESP32 | ESP32-P4 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- |
This is a test app for sdmmc component. This app is for internal use.
# Building
```bash
idf.py build
```
# Running
To run locally:
```bash
idf.py flash monitor
```
The tests will be executed and the summary will be printed:
```
-----------------------
4 Tests 0 Failures 0 Ignored
OK
```
Note, when the Python test script is executed in internal CI, it will test each configuration one by one. When executing this script locally, it will use whichever binary is already built and available in `build` directory.
@@ -0,0 +1,5 @@
idf_component_register(SRCS "test_sdmmc_app.c"
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES esp_blockdev unity sdmmc esp_driver_sdmmc sdmmc_tests
WHOLE_ARCHIVE
)
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "unity.h"
#include "unity_fixture.h"
#include "sd_protocol_defs.h"
#include "sdmmc_cmd.h"
#include "sdmmc_test_begin_end_sd.h"
#include "esp_blockdev.h"
TEST_GROUP(sdmmc);
TEST_SETUP(sdmmc)
{
}
TEST_TEAR_DOWN(sdmmc)
{
}
TEST(sdmmc, test_bdl_interface)
{
sdmmc_card_t card;
int slot = 1;
int width = 1;
int freq_khz = SDMMC_FREQ_DEFAULT;
sdmmc_test_sd_skip_if_board_incompatible(slot, width, freq_khz, 0, 0);
sdmmc_test_sd_begin(slot, width, freq_khz, 0, &card);
uint8_t erased_val = card.scr.erase_mem_state == 0 ? 0 : 255; // SD card marks erased space either 0 or as 0xFF (255)
//get the block-device interface instance
esp_blockdev_handle_t sdmmc_blockdev = NULL;
TEST_ESP_OK(sdmmc_get_blockdev(&card, &sdmmc_blockdev));
TEST_ASSERT_NOT_NULL(sdmmc_blockdev);
const uint64_t target_addr = 512 * 10;// a multiple of 512 (sector size)
const size_t data_size = 1024; // a multiple of 512 (sector size)
//write to the blockdev
uint8_t* test_data = malloc(data_size);
memset((void*)test_data, 'A', data_size);
TEST_ESP_OK(sdmmc_blockdev->ops->write(sdmmc_blockdev, test_data, target_addr, data_size));
//read from the blockdev the data written before
uint8_t* data_buffer = malloc(data_size);
memset((void*)data_buffer, 'X', data_size);
TEST_ESP_OK(sdmmc_blockdev->ops->read(sdmmc_blockdev, data_buffer, data_size, target_addr, data_size));
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
//erase the data from the blockdev and check it's really wiped
TEST_ESP_OK(sdmmc_blockdev->ops->erase(sdmmc_blockdev, target_addr, data_size));
TEST_ESP_OK(sdmmc_blockdev->ops->read(sdmmc_blockdev, data_buffer, data_size, target_addr, data_size));
{
uint8_t ten_erased[10] = {[0 ... 9] = erased_val};
TEST_ASSERT_EQUAL(0, memcmp(ten_erased, data_buffer, 10));
}
free(data_buffer);
free(test_data);
//release the BDL object
TEST_ESP_OK(sdmmc_blockdev->ops->release(sdmmc_blockdev));
sdmmc_test_sd_end(&card);
}
TEST_GROUP_RUNNER(sdmmc)
{
RUN_TEST_CASE(sdmmc, test_bdl_interface)
}
void app_main(void)
{
UNITY_MAIN(sdmmc);
}
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.sdcard
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_sdmmc_extra(dut: Dut) -> None:
dut.expect_unity_test_output()
@@ -0,0 +1,2 @@
# unity framework
CONFIG_UNITY_ENABLE_FIXTURE=y