mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-28 16:46:31 +03:00
feat(fatfs): Added BDL support to FatFS component
This commit is contained in:
@@ -3,17 +3,19 @@ idf_build_get_property(target IDF_TARGET)
|
||||
set(srcs "diskio/diskio.c"
|
||||
"diskio/diskio_rawflash.c"
|
||||
"diskio/diskio_wl.c"
|
||||
"diskio/diskio_bdl.c"
|
||||
"src/ff.c"
|
||||
"src/ffunicode.c")
|
||||
|
||||
set(include_dirs "diskio" "src")
|
||||
|
||||
set(requires "wear_levelling")
|
||||
set(requires "wear_levelling" "esp_blockdev")
|
||||
|
||||
# for linux, we do not have support for sdmmc, for real targets, add respective sources
|
||||
if(${target} STREQUAL "linux")
|
||||
list(APPEND srcs "port/linux/ffsystem.c"
|
||||
"vfs/vfs_fat.c")
|
||||
"vfs/vfs_fat.c"
|
||||
"vfs/vfs_fat_bdl.c")
|
||||
list(APPEND include_dirs "vfs")
|
||||
list(APPEND priv_requires "vfs" "linux")
|
||||
else()
|
||||
@@ -21,7 +23,8 @@ else()
|
||||
"diskio/diskio_sdmmc.c"
|
||||
"vfs/vfs_fat.c"
|
||||
"vfs/vfs_fat_sdmmc.c"
|
||||
"vfs/vfs_fat_spiflash.c")
|
||||
"vfs/vfs_fat_spiflash.c"
|
||||
"vfs/vfs_fat_bdl.c")
|
||||
|
||||
list(APPEND include_dirs "vfs")
|
||||
|
||||
|
||||
274
components/fatfs/diskio/diskio_bdl.c
Normal file
274
components/fatfs/diskio/diskio_bdl.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "diskio_impl.h"
|
||||
#include "ffconf.h"
|
||||
#include "ff.h"
|
||||
#include "esp_log.h"
|
||||
#include "diskio_bdl.h"
|
||||
#include "esp_compiler.h"
|
||||
|
||||
static const char *TAG = "ff_diskio_bdl";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* LCM helpers for FatFS sector-size derivation from BDL geometry */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static inline size_t gcd_size(size_t a, size_t b)
|
||||
{
|
||||
while (b != 0) {
|
||||
size_t t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static inline size_t lcm2_size(size_t a, size_t b)
|
||||
{
|
||||
return (a && b) ? (a / gcd_size(a, b)) * b : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the FatFS logical sector size purely from BDL geometry.
|
||||
*
|
||||
* The sector must be a common multiple of read_size, write_size and
|
||||
* FF_MIN_SS (typically 512). When erase_size can be included without
|
||||
* exceeding FF_MAX_SS the sector is also erase-aligned — correct for
|
||||
* NOR-style devices and optimal for any device. When erase alignment
|
||||
* would push the sector beyond FF_MAX_SS (typical for NAND where
|
||||
* erase blocks >> page size) erase_size is omitted; such devices must
|
||||
* handle erase internally (FTL / wear-levelling layer).
|
||||
*
|
||||
* No BDL flags are inspected — the NOR/NAND distinction is implicit
|
||||
* in the geometry: NOR erase blocks fit within FF_MAX_SS, NAND ones
|
||||
* do not.
|
||||
*
|
||||
* @return valid power-of-two sector size in [FF_MIN_SS, FF_MAX_SS],
|
||||
* or 0 if the geometry is incompatible with FatFS.
|
||||
*/
|
||||
static size_t compute_fs_sector_size(esp_blockdev_handle_t dev)
|
||||
{
|
||||
const esp_blockdev_geometry_t *g = &dev->geometry;
|
||||
|
||||
size_t result = (size_t)FF_MIN_SS;
|
||||
|
||||
if (g->read_size > 1) {
|
||||
result = lcm2_size(result, g->read_size);
|
||||
}
|
||||
if (g->write_size > 1) {
|
||||
result = lcm2_size(result, g->write_size);
|
||||
}
|
||||
|
||||
if (g->erase_size > 1) {
|
||||
size_t with_erase = lcm2_size(result, g->erase_size);
|
||||
if (with_erase && with_erase <= FF_MAX_SS) {
|
||||
result = with_erase;
|
||||
}
|
||||
}
|
||||
|
||||
if (result < FF_MIN_SS || result > FF_MAX_SS || (result & (result - 1)) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct {
|
||||
esp_blockdev_handle_t handle;
|
||||
size_t fs_sector_size;
|
||||
} bdl_drive_t;
|
||||
|
||||
static bdl_drive_t s_bdl_drives[FF_VOLUMES];
|
||||
|
||||
static DSTATUS ff_bdl_initialize(BYTE pdrv)
|
||||
{
|
||||
esp_blockdev_handle_t dev = s_bdl_drives[pdrv].handle;
|
||||
assert(dev != ESP_BLOCKDEV_HANDLE_INVALID);
|
||||
if (dev->device_flags.read_only) {
|
||||
return STA_PROTECT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DSTATUS ff_bdl_status(BYTE pdrv)
|
||||
{
|
||||
esp_blockdev_handle_t dev = s_bdl_drives[pdrv].handle;
|
||||
assert(dev != ESP_BLOCKDEV_HANDLE_INVALID);
|
||||
if (dev->device_flags.read_only) {
|
||||
return STA_PROTECT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DRESULT ff_bdl_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
bdl_drive_t *drv = &s_bdl_drives[pdrv];
|
||||
assert(drv->handle != ESP_BLOCKDEV_HANDLE_INVALID);
|
||||
size_t sec_size = drv->fs_sector_size;
|
||||
ESP_LOGV(TAG, "read - pdrv=%u, sector=%lu, count=%u, sec_size=%u",
|
||||
(unsigned)pdrv, (unsigned long)sector, (unsigned)count, (unsigned)sec_size);
|
||||
|
||||
esp_err_t err = drv->handle->ops->read(drv->handle, buff, count * sec_size,
|
||||
(uint64_t)sector * sec_size, count * sec_size);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "BDL read failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
static DRESULT ff_bdl_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
bdl_drive_t *drv = &s_bdl_drives[pdrv];
|
||||
assert(drv->handle != ESP_BLOCKDEV_HANDLE_INVALID);
|
||||
|
||||
if (drv->handle->device_flags.read_only) {
|
||||
return RES_WRPRT;
|
||||
}
|
||||
|
||||
size_t sec_size = drv->fs_sector_size;
|
||||
uint64_t addr = (uint64_t)sector * sec_size;
|
||||
size_t len = count * sec_size;
|
||||
|
||||
ESP_LOGV(TAG, "write - pdrv=%u, sector=%lu, count=%u", (unsigned)pdrv, (unsigned long)sector, (unsigned)count);
|
||||
|
||||
if (drv->handle->device_flags.erase_before_write || drv->handle->device_flags.and_type_write) {
|
||||
size_t erase_sz = drv->handle->geometry.erase_size;
|
||||
if ((addr % erase_sz == 0) && (len % erase_sz == 0)) {
|
||||
esp_err_t err = drv->handle->ops->erase(drv->handle, addr, len);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "BDL erase failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t err = drv->handle->ops->write(drv->handle, buff, addr, len);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "BDL write failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
static DRESULT ff_bdl_ioctl(BYTE pdrv, BYTE cmd, void *buff)
|
||||
{
|
||||
bdl_drive_t *drv = &s_bdl_drives[pdrv];
|
||||
assert(drv->handle != ESP_BLOCKDEV_HANDLE_INVALID);
|
||||
ESP_LOGV(TAG, "ioctl: cmd=%u", (unsigned)cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
if (drv->handle->ops->sync) {
|
||||
esp_err_t err = drv->handle->ops->sync(drv->handle);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "BDL sync failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
}
|
||||
return RES_OK;
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *)buff) = (DWORD)(drv->handle->geometry.disk_size / drv->fs_sector_size);
|
||||
return RES_OK;
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *)buff) = (WORD)drv->fs_sector_size;
|
||||
return RES_OK;
|
||||
case GET_BLOCK_SIZE: {
|
||||
size_t erase_sz = drv->handle->geometry.erase_size;
|
||||
*((DWORD *)buff) = (erase_sz >= drv->fs_sector_size)
|
||||
? (DWORD)(erase_sz / drv->fs_sector_size)
|
||||
: 1;
|
||||
return RES_OK;
|
||||
}
|
||||
#if FF_USE_TRIM
|
||||
case CTRL_TRIM: {
|
||||
if (drv->handle->ops->ioctl == NULL) {
|
||||
return RES_OK;
|
||||
}
|
||||
size_t sec_size = drv->fs_sector_size;
|
||||
DWORD start_sector = *((DWORD *)buff);
|
||||
DWORD end_sector = *((DWORD *)buff + 1);
|
||||
esp_blockdev_cmd_arg_erase_t erase_arg = {
|
||||
.start_addr = (uint64_t)start_sector * sec_size,
|
||||
.erase_len = (size_t)(end_sector - start_sector + 1) * sec_size,
|
||||
};
|
||||
esp_err_t err = drv->handle->ops->ioctl(drv->handle, ESP_BLOCKDEV_CMD_MARK_DELETED, &erase_arg);
|
||||
if (unlikely(err != ESP_OK && err != ESP_ERR_NOT_SUPPORTED)) {
|
||||
ESP_LOGE(TAG, "BDL TRIM ioctl failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
esp_err_t ff_diskio_register_bdl(BYTE pdrv, esp_blockdev_handle_t bdl_handle)
|
||||
{
|
||||
if (pdrv >= FF_VOLUMES) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (bdl_handle == ESP_BLOCKDEV_HANDLE_INVALID) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (bdl_handle->geometry.read_size == 0 || bdl_handle->geometry.disk_size == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t fs_sec = compute_fs_sector_size(bdl_handle);
|
||||
if (fs_sec == 0) {
|
||||
ESP_LOGE(TAG, "BDL geometry incompatible with FatFS "
|
||||
"(read=%u, write=%u, erase=%u, FF_MAX_SS=%u)",
|
||||
(unsigned)bdl_handle->geometry.read_size,
|
||||
(unsigned)bdl_handle->geometry.write_size,
|
||||
(unsigned)bdl_handle->geometry.erase_size,
|
||||
(unsigned)FF_MAX_SS);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
static const ff_diskio_impl_t bdl_impl = {
|
||||
.init = &ff_bdl_initialize,
|
||||
.status = &ff_bdl_status,
|
||||
.read = &ff_bdl_read,
|
||||
.write = &ff_bdl_write,
|
||||
.ioctl = &ff_bdl_ioctl
|
||||
};
|
||||
|
||||
s_bdl_drives[pdrv] = (bdl_drive_t){
|
||||
.handle = bdl_handle,
|
||||
.fs_sector_size = fs_sec,
|
||||
};
|
||||
ff_diskio_register(pdrv, &bdl_impl);
|
||||
ESP_LOGD(TAG, "pdrv=%u registered, fs_sector_size=%u, erase_size=%u, disk_size=%llu",
|
||||
(unsigned)pdrv, (unsigned)fs_sec,
|
||||
(unsigned)bdl_handle->geometry.erase_size,
|
||||
(unsigned long long)bdl_handle->geometry.disk_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
BYTE ff_diskio_get_pdrv_bdl(esp_blockdev_handle_t bdl_handle)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (bdl_handle == s_bdl_drives[i].handle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void ff_diskio_clear_pdrv_bdl(esp_blockdev_handle_t bdl_handle)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (bdl_handle == s_bdl_drives[i].handle) {
|
||||
s_bdl_drives[i] = (bdl_drive_t){0};
|
||||
}
|
||||
}
|
||||
}
|
||||
66
components/fatfs/diskio/diskio_bdl.h
Normal file
66
components/fatfs/diskio/diskio_bdl.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_blockdev.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief Register a BDL (Block Device Layer) device as a FatFS diskio driver
|
||||
*
|
||||
* The FatFS logical sector size is derived from BDL geometry at registration
|
||||
* time as: LCM(FF_MIN_SS, read_size, write_size [, erase_size]).
|
||||
* erase_size is included only when the result still fits within FF_MAX_SS;
|
||||
* this makes the sector erase-aligned for NOR-style devices (small erase
|
||||
* blocks) and page-aligned for NAND-style devices (large erase blocks,
|
||||
* erase handled internally by an FTL/WL layer). No BDL flags are
|
||||
* inspected — the NOR/NAND distinction is implicit in the geometry.
|
||||
*
|
||||
* The computed sector size is cached and used for all subsequent I/O.
|
||||
* GET_BLOCK_SIZE returns erase_size / sector_size (minimum 1) so that
|
||||
* FatFS can align clusters to erase boundaries.
|
||||
*
|
||||
* Erase-before-write is performed when device_flags.erase_before_write or
|
||||
* device_flags.and_type_write is set and the write range is aligned to
|
||||
* geometry.erase_size. Read-only
|
||||
* devices are supported (write returns RES_WRPRT, status returns
|
||||
* STA_PROTECT).
|
||||
*
|
||||
* @param pdrv Drive number (0..FF_VOLUMES-1)
|
||||
* @param bdl_handle BDL device handle providing the storage
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if pdrv is out of range, bdl_handle is invalid,
|
||||
* or geometry is incompatible with FatFS (sector size not a power of
|
||||
* two or exceeds FF_MAX_SS)
|
||||
*/
|
||||
esp_err_t ff_diskio_register_bdl(unsigned char pdrv, esp_blockdev_handle_t bdl_handle);
|
||||
|
||||
/**
|
||||
* @brief Get the drive number associated with a BDL handle
|
||||
*
|
||||
* @param bdl_handle BDL device handle to look up
|
||||
*
|
||||
* @return Drive number (0..FF_VOLUMES-1) or 0xFF if not found
|
||||
*/
|
||||
unsigned char ff_diskio_get_pdrv_bdl(esp_blockdev_handle_t bdl_handle);
|
||||
|
||||
/**
|
||||
* @brief Clear the internal BDL handle association for a given handle
|
||||
*
|
||||
* @param bdl_handle BDL device handle to clear
|
||||
*/
|
||||
void ff_diskio_clear_pdrv_bdl(esp_blockdev_handle_t bdl_handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -5,3 +5,7 @@ components/fatfs/host_test:
|
||||
- if: IDF_TARGET == "esp32p4"
|
||||
temporary: true
|
||||
reason: test not pass, should be re-enable # TODO: IDF-8980
|
||||
|
||||
components/fatfs/host_test/bdl:
|
||||
enable:
|
||||
- if: IDF_TARGET == "linux"
|
||||
|
||||
7
components/fatfs/host_test/bdl/CMakeLists.txt
Normal file
7
components/fatfs/host_test/bdl/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
|
||||
|
||||
project(fatfs_bdl_host_test)
|
||||
6
components/fatfs/host_test/bdl/main/CMakeLists.txt
Normal file
6
components/fatfs/host_test/bdl/main/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
idf_component_register(SRCS "test_fatfs_bdl.cpp"
|
||||
REQUIRES fatfs vfs esp_blockdev esp_blockdev_util wear_levelling
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain)
|
||||
2
components/fatfs/host_test/bdl/main/idf_component.yml
Normal file
2
components/fatfs/host_test/bdl/main/idf_component.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/catch2: "^3.4.0"
|
||||
276
components/fatfs/host_test/bdl/main/test_fatfs_bdl.cpp
Normal file
276
components/fatfs/host_test/bdl/main/test_fatfs_bdl.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "ff.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_bdl.h"
|
||||
#include "esp_blockdev.h"
|
||||
#include "esp_blockdev/memory.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "esp_vfs.h"
|
||||
|
||||
#include "esp_partition.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "diskio_wl.h"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test 1: FatFS directly on memory BDL (no wear-levelling) */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("BDL: Create volume on memory BDL, write and read back data", "[fatfs][bdl]")
|
||||
{
|
||||
static uint8_t backing[256 * 1024];
|
||||
memset(backing, 0xFF, sizeof(backing));
|
||||
|
||||
const esp_blockdev_geometry_t geometry = {
|
||||
.disk_size = sizeof(backing),
|
||||
.read_size = 1,
|
||||
.write_size = 1,
|
||||
.erase_size = 4096,
|
||||
.recommended_write_size = 0,
|
||||
.recommended_read_size = 0,
|
||||
.recommended_erase_size = 0,
|
||||
};
|
||||
|
||||
esp_blockdev_handle_t mem_dev = NULL;
|
||||
REQUIRE(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing),
|
||||
&geometry, false, &mem_dev) == ESP_OK);
|
||||
|
||||
BYTE pdrv;
|
||||
REQUIRE(ff_diskio_get_drive(&pdrv) == ESP_OK);
|
||||
REQUIRE(ff_diskio_register_bdl(pdrv, mem_dev) == ESP_OK);
|
||||
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
LBA_t part_list[] = {100, 0, 0, 0};
|
||||
BYTE work_area[FF_MAX_SS];
|
||||
|
||||
REQUIRE(f_fdisk(pdrv, part_list, work_area) == FR_OK);
|
||||
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, 0};
|
||||
REQUIRE(f_mkfs(drv, &opt, work_area, sizeof(work_area)) == FR_OK);
|
||||
|
||||
FATFS fs;
|
||||
REQUIRE(f_mount(&fs, drv, 1) == FR_OK);
|
||||
|
||||
FIL file;
|
||||
UINT bw;
|
||||
REQUIRE(f_open(&file, "test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE) == FR_OK);
|
||||
|
||||
uint32_t data_size = 1000;
|
||||
char *data = (char *)malloc(data_size);
|
||||
char *read_buf = (char *)malloc(data_size);
|
||||
for (uint32_t i = 0; i < data_size; i += sizeof(i)) {
|
||||
*((uint32_t *)(data + i)) = i;
|
||||
}
|
||||
|
||||
REQUIRE(f_write(&file, data, data_size, &bw) == FR_OK);
|
||||
REQUIRE(bw == data_size);
|
||||
|
||||
REQUIRE(f_lseek(&file, 0) == FR_OK);
|
||||
REQUIRE(f_read(&file, read_buf, data_size, &bw) == FR_OK);
|
||||
REQUIRE(bw == data_size);
|
||||
REQUIRE(memcmp(data, read_buf, data_size) == 0);
|
||||
|
||||
REQUIRE(f_close(&file) == FR_OK);
|
||||
REQUIRE(f_mount(0, drv, 0) == FR_OK);
|
||||
|
||||
free(read_buf);
|
||||
free(data);
|
||||
ff_diskio_unregister(pdrv);
|
||||
ff_diskio_clear_pdrv_bdl(mem_dev);
|
||||
mem_dev->ops->release(mem_dev);
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test 2: FatFS BDL diskio driver registration and geometry */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("BDL: Geometry is correctly reported via ioctl", "[fatfs][bdl]")
|
||||
{
|
||||
static uint8_t backing[128 * 1024];
|
||||
memset(backing, 0xFF, sizeof(backing));
|
||||
|
||||
const esp_blockdev_geometry_t geometry = {
|
||||
.disk_size = sizeof(backing),
|
||||
.read_size = 1,
|
||||
.write_size = 1,
|
||||
.erase_size = 512,
|
||||
.recommended_write_size = 0,
|
||||
.recommended_read_size = 0,
|
||||
.recommended_erase_size = 0,
|
||||
};
|
||||
|
||||
esp_blockdev_handle_t mem_dev = NULL;
|
||||
REQUIRE(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing),
|
||||
&geometry, false, &mem_dev) == ESP_OK);
|
||||
|
||||
BYTE pdrv;
|
||||
REQUIRE(ff_diskio_get_drive(&pdrv) == ESP_OK);
|
||||
REQUIRE(ff_diskio_register_bdl(pdrv, mem_dev) == ESP_OK);
|
||||
|
||||
WORD sec_size = 0;
|
||||
REQUIRE(ff_disk_ioctl(pdrv, GET_SECTOR_SIZE, &sec_size) == RES_OK);
|
||||
REQUIRE(sec_size == 512);
|
||||
|
||||
DWORD sec_count = 0;
|
||||
REQUIRE(ff_disk_ioctl(pdrv, GET_SECTOR_COUNT, &sec_count) == RES_OK);
|
||||
REQUIRE(sec_count == sizeof(backing) / 512);
|
||||
|
||||
REQUIRE(ff_disk_ioctl(pdrv, CTRL_SYNC, NULL) == RES_OK);
|
||||
|
||||
ff_diskio_unregister(pdrv);
|
||||
ff_diskio_clear_pdrv_bdl(mem_dev);
|
||||
mem_dev->ops->release(mem_dev);
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test 3: FatFS BDL pdrv lookup functions */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("BDL: pdrv lookup and clear", "[fatfs][bdl]")
|
||||
{
|
||||
static uint8_t backing[64 * 1024];
|
||||
memset(backing, 0xFF, sizeof(backing));
|
||||
|
||||
const esp_blockdev_geometry_t geometry = {
|
||||
.disk_size = sizeof(backing),
|
||||
.read_size = 1,
|
||||
.write_size = 1,
|
||||
.erase_size = 4096,
|
||||
.recommended_write_size = 0,
|
||||
.recommended_read_size = 0,
|
||||
.recommended_erase_size = 0,
|
||||
};
|
||||
|
||||
esp_blockdev_handle_t mem_dev = NULL;
|
||||
REQUIRE(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing),
|
||||
&geometry, false, &mem_dev) == ESP_OK);
|
||||
|
||||
REQUIRE(ff_diskio_get_pdrv_bdl(mem_dev) == 0xff);
|
||||
|
||||
BYTE pdrv;
|
||||
REQUIRE(ff_diskio_get_drive(&pdrv) == ESP_OK);
|
||||
REQUIRE(ff_diskio_register_bdl(pdrv, mem_dev) == ESP_OK);
|
||||
|
||||
REQUIRE(ff_diskio_get_pdrv_bdl(mem_dev) == pdrv);
|
||||
|
||||
ff_diskio_clear_pdrv_bdl(mem_dev);
|
||||
REQUIRE(ff_diskio_get_pdrv_bdl(mem_dev) == 0xff);
|
||||
|
||||
ff_diskio_unregister(pdrv);
|
||||
mem_dev->ops->release(mem_dev);
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test 4: FatFS BDL VFS mount/unmount via esp_vfs_fat_bdl_mount() */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("BDL VFS: mount, write and read via POSIX API", "[fatfs][bdl][vfs]")
|
||||
{
|
||||
static uint8_t backing[256 * 1024];
|
||||
memset(backing, 0xFF, sizeof(backing));
|
||||
|
||||
const esp_blockdev_geometry_t geometry = {
|
||||
.disk_size = sizeof(backing),
|
||||
.read_size = 1,
|
||||
.write_size = 1,
|
||||
.erase_size = 4096,
|
||||
.recommended_write_size = 0,
|
||||
.recommended_read_size = 0,
|
||||
.recommended_erase_size = 0,
|
||||
};
|
||||
|
||||
esp_blockdev_handle_t mem_dev = NULL;
|
||||
REQUIRE(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing),
|
||||
&geometry, false, &mem_dev) == ESP_OK);
|
||||
|
||||
esp_vfs_fat_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
};
|
||||
REQUIRE(esp_vfs_fat_bdl_mount("/bdl", mem_dev, &mount_config) == ESP_OK);
|
||||
|
||||
const char *test_str = "BDL FatFS test data!\n";
|
||||
const char *filename = "/bdl/hello.txt";
|
||||
|
||||
int fd = open(filename, O_CREAT | O_RDWR, 0777);
|
||||
REQUIRE(fd != -1);
|
||||
ssize_t sz = write(fd, test_str, strlen(test_str));
|
||||
REQUIRE(sz == (ssize_t)strlen(test_str));
|
||||
REQUIRE(0 == close(fd));
|
||||
|
||||
fd = open(filename, O_RDONLY);
|
||||
REQUIRE(fd != -1);
|
||||
char buf[64] = {};
|
||||
sz = read(fd, buf, sizeof(buf));
|
||||
REQUIRE(sz == (ssize_t)strlen(test_str));
|
||||
REQUIRE(0 == memcmp(buf, test_str, strlen(test_str)));
|
||||
REQUIRE(0 == close(fd));
|
||||
|
||||
REQUIRE(esp_vfs_fat_bdl_unmount("/bdl", mem_dev) == ESP_OK);
|
||||
mem_dev->ops->release(mem_dev);
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test 5: FatFS BDL on partition (via WL legacy path for reference) */
|
||||
/* Uses the classic WL path alongside BDL to show they coexist. */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("BDL and legacy WL coexist on different drives", "[fatfs][bdl]")
|
||||
{
|
||||
static uint8_t backing[256 * 1024];
|
||||
memset(backing, 0xFF, sizeof(backing));
|
||||
|
||||
const esp_blockdev_geometry_t geometry = {
|
||||
.disk_size = sizeof(backing),
|
||||
.read_size = 1,
|
||||
.write_size = 1,
|
||||
.erase_size = 4096,
|
||||
.recommended_write_size = 0,
|
||||
.recommended_read_size = 0,
|
||||
.recommended_erase_size = 0,
|
||||
};
|
||||
|
||||
esp_blockdev_handle_t mem_dev = NULL;
|
||||
REQUIRE(esp_blockdev_memory_get_from_buffer(backing, sizeof(backing),
|
||||
&geometry, false, &mem_dev) == ESP_OK);
|
||||
|
||||
const esp_partition_t *partition = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage");
|
||||
REQUIRE(partition != NULL);
|
||||
|
||||
wl_handle_t wl_handle;
|
||||
REQUIRE(wl_mount(partition, &wl_handle) == ESP_OK);
|
||||
|
||||
BYTE pdrv_wl;
|
||||
REQUIRE(ff_diskio_get_drive(&pdrv_wl) == ESP_OK);
|
||||
REQUIRE(ff_diskio_register_wl_partition(pdrv_wl, wl_handle) == ESP_OK);
|
||||
|
||||
BYTE pdrv_bdl;
|
||||
REQUIRE(ff_diskio_get_drive(&pdrv_bdl) == ESP_OK);
|
||||
REQUIRE(ff_diskio_register_bdl(pdrv_bdl, mem_dev) == ESP_OK);
|
||||
|
||||
REQUIRE(pdrv_wl != pdrv_bdl);
|
||||
|
||||
WORD sec_size_wl = 0;
|
||||
REQUIRE(ff_disk_ioctl(pdrv_wl, GET_SECTOR_SIZE, &sec_size_wl) == RES_OK);
|
||||
|
||||
WORD sec_size_bdl = 0;
|
||||
REQUIRE(ff_disk_ioctl(pdrv_bdl, GET_SECTOR_SIZE, &sec_size_bdl) == RES_OK);
|
||||
REQUIRE(sec_size_bdl == 4096);
|
||||
|
||||
ff_diskio_unregister(pdrv_bdl);
|
||||
ff_diskio_clear_pdrv_bdl(mem_dev);
|
||||
ff_diskio_unregister(pdrv_wl);
|
||||
ff_diskio_clear_pdrv_wl(wl_handle);
|
||||
REQUIRE(wl_unmount(wl_handle) == ESP_OK);
|
||||
mem_dev->ops->release(mem_dev);
|
||||
}
|
||||
6
components/fatfs/host_test/bdl/partition_table.csv
Normal file
6
components/fatfs/host_test/bdl/partition_table.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 1M,
|
||||
storage2, data, fat, , 32k,
|
||||
|
11
components/fatfs/host_test/bdl/pytest_fatfs_bdl_linux.py
Normal file
11
components/fatfs/host_test/bdl/pytest_fatfs_bdl_linux.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_fatfs_bdl_linux(dut: Dut) -> None:
|
||||
dut.expect_exact('All tests passed', timeout=120)
|
||||
11
components/fatfs/host_test/bdl/sdkconfig.defaults
Normal file
11
components/fatfs/host_test/bdl/sdkconfig.defaults
Normal file
@@ -0,0 +1,11 @@
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_WL_SECTOR_SIZE=4096
|
||||
CONFIG_LOG_DEFAULT_LEVEL=3
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv"
|
||||
CONFIG_MMU_PAGE_SIZE=0X10000
|
||||
CONFIG_ESP_PARTITION_ENABLE_STATS=y
|
||||
CONFIG_FATFS_VOLUME_COUNT=3
|
||||
@@ -1,5 +1,15 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/fatfs/test_apps/bdl:
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32"
|
||||
reason: only one target needed
|
||||
depends_components:
|
||||
- esp_blockdev
|
||||
- esp_partition
|
||||
- fatfs
|
||||
- vfs
|
||||
|
||||
components/fatfs/test_apps/dyn_buffers:
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
| 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 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# fatfs component target tests
|
||||
|
||||
|
||||
7
components/fatfs/test_apps/bdl/CMakeLists.txt
Normal file
7
components/fatfs/test_apps/bdl/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(test_fatfs_bdl)
|
||||
4
components/fatfs/test_apps/bdl/main/CMakeLists.txt
Normal file
4
components/fatfs/test_apps/bdl/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "test_fatfs_bdl.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity fatfs vfs esp_blockdev esp_partition
|
||||
WHOLE_ARCHIVE)
|
||||
220
components/fatfs/test_apps/bdl/main/test_fatfs_bdl.c
Normal file
220
components/fatfs/test_apps/bdl/main/test_fatfs_bdl.c
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "unity.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_blockdev.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "ff.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_bdl.h"
|
||||
|
||||
static const char *TAG = "test_fatfs_bdl";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Helper: create partition BDL and erase it */
|
||||
/* ===================================================================== */
|
||||
|
||||
static esp_blockdev_handle_t s_test_bdl = NULL;
|
||||
|
||||
static void test_setup_partition_bdl(const char *label)
|
||||
{
|
||||
esp_err_t err = esp_partition_get_blockdev(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT,
|
||||
label, &s_test_bdl);
|
||||
TEST_ESP_OK(err);
|
||||
TEST_ASSERT_NOT_NULL(s_test_bdl);
|
||||
|
||||
ESP_LOGI(TAG, "Partition BDL: disk_size=%llu, erase_size=%u",
|
||||
(unsigned long long)s_test_bdl->geometry.disk_size,
|
||||
(unsigned)s_test_bdl->geometry.erase_size);
|
||||
}
|
||||
|
||||
static void test_teardown_partition_bdl(void)
|
||||
{
|
||||
if (s_test_bdl) {
|
||||
s_test_bdl->ops->release(s_test_bdl);
|
||||
s_test_bdl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test: BDL diskio low-level on partition BDL */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("(BDL) diskio register, format, write and read on partition", "[fatfs][bdl]")
|
||||
{
|
||||
test_setup_partition_bdl("storage");
|
||||
|
||||
BYTE pdrv;
|
||||
TEST_ESP_OK(ff_diskio_get_drive(&pdrv));
|
||||
TEST_ESP_OK(ff_diskio_register_bdl(pdrv, s_test_bdl));
|
||||
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
|
||||
WORD sec_size = 0;
|
||||
TEST_ASSERT_EQUAL(RES_OK, ff_disk_ioctl(pdrv, GET_SECTOR_SIZE, &sec_size));
|
||||
ESP_LOGI(TAG, "Sector size: %u", (unsigned)sec_size);
|
||||
|
||||
DWORD sec_count = 0;
|
||||
TEST_ASSERT_EQUAL(RES_OK, ff_disk_ioctl(pdrv, GET_SECTOR_COUNT, &sec_count));
|
||||
ESP_LOGI(TAG, "Sector count: %lu", (unsigned long)sec_count);
|
||||
|
||||
BYTE work_area[FF_MAX_SS];
|
||||
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 2, 0, 0, sec_size};
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_mkfs(drv, &opt, work_area, sizeof(work_area)));
|
||||
|
||||
FATFS fs;
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_mount(&fs, drv, 1));
|
||||
|
||||
FIL file;
|
||||
UINT bw;
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_open(&file, "test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE));
|
||||
|
||||
const char *test_data = "Hello from FatFS over BDL partition!";
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_write(&file, test_data, strlen(test_data), &bw));
|
||||
TEST_ASSERT_EQUAL(strlen(test_data), bw);
|
||||
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_lseek(&file, 0));
|
||||
|
||||
char read_buf[128] = {};
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_read(&file, read_buf, sizeof(read_buf) - 1, &bw));
|
||||
TEST_ASSERT_EQUAL(strlen(test_data), bw);
|
||||
TEST_ASSERT_EQUAL_STRING(test_data, read_buf);
|
||||
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_close(&file));
|
||||
TEST_ASSERT_EQUAL(FR_OK, f_mount(0, drv, 0));
|
||||
|
||||
ff_diskio_unregister(pdrv);
|
||||
ff_diskio_clear_pdrv_bdl(s_test_bdl);
|
||||
test_teardown_partition_bdl();
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test: BDL VFS mount/unmount on partition BDL */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("(BDL) VFS mount, file operations and unmount", "[fatfs][bdl]")
|
||||
{
|
||||
test_setup_partition_bdl("storage");
|
||||
|
||||
esp_vfs_fat_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_bdl_mount("/bdltest", s_test_bdl, &mount_config));
|
||||
|
||||
const char *hello_str = "Hello from BDL VFS FatFS!\n";
|
||||
const char *filename = "/bdltest/hello.txt";
|
||||
|
||||
FILE *f = fopen(filename, "w");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
fprintf(f, "%s", hello_str);
|
||||
fclose(f);
|
||||
|
||||
f = fopen(filename, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
char buf[128] = {};
|
||||
TEST_ASSERT_NOT_NULL(fgets(buf, sizeof(buf), f));
|
||||
fclose(f);
|
||||
TEST_ASSERT_EQUAL_STRING(hello_str, buf);
|
||||
|
||||
struct stat st;
|
||||
TEST_ASSERT_EQUAL(0, stat(filename, &st));
|
||||
TEST_ASSERT_EQUAL(strlen(hello_str), st.st_size);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, unlink(filename));
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_bdl_unmount("/bdltest", s_test_bdl));
|
||||
test_teardown_partition_bdl();
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test: BDL geometry is correct for partition BDL */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("(BDL) partition BDL geometry matches partition size", "[fatfs][bdl]")
|
||||
{
|
||||
const esp_partition_t *part = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage");
|
||||
TEST_ASSERT_NOT_NULL(part);
|
||||
|
||||
test_setup_partition_bdl("storage");
|
||||
|
||||
TEST_ASSERT_EQUAL(part->size, s_test_bdl->geometry.disk_size);
|
||||
TEST_ASSERT(s_test_bdl->geometry.erase_size > 0);
|
||||
TEST_ASSERT(s_test_bdl->geometry.read_size > 0);
|
||||
TEST_ASSERT_EQUAL(0, s_test_bdl->geometry.disk_size % s_test_bdl->geometry.erase_size);
|
||||
|
||||
test_teardown_partition_bdl();
|
||||
}
|
||||
|
||||
/* ===================================================================== */
|
||||
/* Test: Two BDL volumes on separate partitions */
|
||||
/* ===================================================================== */
|
||||
|
||||
TEST_CASE("(BDL) two BDL volumes coexist", "[fatfs][bdl]")
|
||||
{
|
||||
esp_blockdev_handle_t bdl1 = NULL;
|
||||
esp_blockdev_handle_t bdl2 = NULL;
|
||||
|
||||
TEST_ESP_OK(esp_partition_get_blockdev(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT,
|
||||
"storage", &bdl1));
|
||||
TEST_ESP_OK(esp_partition_get_blockdev(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT,
|
||||
"storage2", &bdl2));
|
||||
|
||||
esp_vfs_fat_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_bdl_mount("/bdl1", bdl1, &mount_config));
|
||||
TEST_ESP_OK(esp_vfs_fat_bdl_mount("/bdl2", bdl2, &mount_config));
|
||||
|
||||
FILE *f1 = fopen("/bdl1/a.txt", "w");
|
||||
TEST_ASSERT_NOT_NULL(f1);
|
||||
fprintf(f1, "vol1");
|
||||
fclose(f1);
|
||||
|
||||
FILE *f2 = fopen("/bdl2/b.txt", "w");
|
||||
TEST_ASSERT_NOT_NULL(f2);
|
||||
fprintf(f2, "vol2");
|
||||
fclose(f2);
|
||||
|
||||
char buf[16] = {};
|
||||
f1 = fopen("/bdl1/a.txt", "r");
|
||||
TEST_ASSERT_NOT_NULL(f1);
|
||||
fgets(buf, sizeof(buf), f1);
|
||||
fclose(f1);
|
||||
TEST_ASSERT_EQUAL_STRING("vol1", buf);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
f2 = fopen("/bdl2/b.txt", "r");
|
||||
TEST_ASSERT_NOT_NULL(f2);
|
||||
fgets(buf, sizeof(buf), f2);
|
||||
fclose(f2);
|
||||
TEST_ASSERT_EQUAL_STRING("vol2", buf);
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_bdl_unmount("/bdl1", bdl1));
|
||||
TEST_ESP_OK(esp_vfs_fat_bdl_unmount("/bdl2", bdl2));
|
||||
|
||||
bdl1->ops->release(bdl1);
|
||||
bdl2->ops->release(bdl2);
|
||||
}
|
||||
4
components/fatfs/test_apps/bdl/partitions.csv
Normal file
4
components/fatfs/test_apps/bdl/partitions.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
factory, app, factory, 0x10000, 768k,
|
||||
storage, data, fat, , 528k,
|
||||
storage2, data, fat, , 528k,
|
||||
|
10
components/fatfs/test_apps/bdl/pytest_fatfs_bdl.py
Normal file
10
components/fatfs/test_apps/bdl/pytest_fatfs_bdl.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.generic
|
||||
def test_fatfs_bdl(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(timeout=120)
|
||||
14
components/fatfs/test_apps/bdl/sdkconfig.defaults
Normal file
14
components/fatfs/test_apps/bdl/sdkconfig.defaults
Normal file
@@ -0,0 +1,14 @@
|
||||
# General options for additional checks
|
||||
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
|
||||
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
|
||||
CONFIG_COMPILER_STACK_CHECK=y
|
||||
|
||||
# disable task watchdog since this app uses an interactive menu
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
# use custom partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -13,6 +13,7 @@
|
||||
#endif
|
||||
#include "ff.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "esp_blockdev.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -403,6 +404,48 @@ esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* partition_label);
|
||||
|
||||
/**
|
||||
* @brief Convenience function to mount a FatFS volume on a BDL (Block Device Layer) device
|
||||
*
|
||||
* The FatFS logical sector size is derived from BDL geometry as
|
||||
* LCM(FF_MIN_SS, read_size, write_size [, erase_size]). erase_size is
|
||||
* included when it fits within FF_MAX_SS, making the sector erase-aligned
|
||||
* for NOR-style devices and page-aligned for NAND-style devices (where
|
||||
* the FTL/WL layer handles erase internally).
|
||||
*
|
||||
* The caller is responsible for constructing the BDL stack (e.g. partition BDL ->
|
||||
* WL BDL) before calling this function. Read-only devices are detected
|
||||
* automatically.
|
||||
*
|
||||
* @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
|
||||
* @param bdl_handle BDL device handle providing the storage
|
||||
* @param mount_config pointer to structure with extra parameters for mounting FATFS
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if any of the arguments is invalid
|
||||
* - ESP_ERR_NO_MEM if memory can not be allocated or no free drives
|
||||
* - ESP_FAIL if partition can not be mounted
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_bdl_mount(const char *base_path,
|
||||
esp_blockdev_handle_t bdl_handle,
|
||||
const esp_vfs_fat_mount_config_t *mount_config);
|
||||
|
||||
/**
|
||||
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_bdl_mount
|
||||
*
|
||||
* @note This function does NOT release the BDL device handle — the caller owns
|
||||
* the BDL stack lifecycle.
|
||||
*
|
||||
* @param base_path path where partition was registered (e.g. "/spiflash")
|
||||
* @param bdl_handle BDL device handle used during mount
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_bdl_mount hasn't been called
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_bdl_unmount(const char *base_path, esp_blockdev_handle_t bdl_handle);
|
||||
|
||||
/**
|
||||
* @brief Get information for FATFS partition
|
||||
*
|
||||
|
||||
204
components/fatfs/vfs/vfs_fat_bdl.c
Normal file
204
components/fatfs/vfs/vfs_fat_bdl.c
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "vfs_fat_internal.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_bdl.h"
|
||||
|
||||
static const char *TAG = "vfs_fat_bdl";
|
||||
|
||||
static vfs_fat_bdl_ctx_t *s_bdl_ctx[FF_VOLUMES] = {};
|
||||
|
||||
extern esp_err_t esp_vfs_set_readonly_flag(const char *base_path);
|
||||
|
||||
static bool get_ctx_id_by_bdl(esp_blockdev_handle_t bdl, uint32_t *out_id)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (s_bdl_ctx[i] && s_bdl_ctx[i]->bdl_handle == bdl) {
|
||||
*out_id = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t get_unused_ctx_id(void)
|
||||
{
|
||||
for (uint32_t i = 0; i < FF_VOLUMES; i++) {
|
||||
if (!s_bdl_ctx[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return FF_VOLUMES;
|
||||
}
|
||||
|
||||
static esp_err_t try_mount_rw(FATFS *fs, const char *drv,
|
||||
const esp_vfs_fat_mount_config_t *mount_config,
|
||||
vfs_fat_x_ctx_flags_t *out_flags,
|
||||
size_t sec_num, size_t sec_size)
|
||||
{
|
||||
FRESULT fresult = f_mount(fs, drv, 1);
|
||||
if (fresult == FR_OK) {
|
||||
if (out_flags) {
|
||||
*out_flags &= ~FORMATTED_DURING_LAST_MOUNT;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool recoverable = (fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR);
|
||||
if (!recoverable || !mount_config->format_if_mount_failed) {
|
||||
ESP_LOGE(TAG, "f_mount failed (%d)", fresult);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "f_mount failed (%d), formatting...", fresult);
|
||||
|
||||
const size_t workbuf_size = 4096;
|
||||
void *workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
|
||||
sec_size, mount_config->allocation_unit_size);
|
||||
ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
|
||||
|
||||
UINT root_dir_entries = (sec_size == 512) ? 16 : 128;
|
||||
const MKFS_PARM opt = {
|
||||
(BYTE)(FM_ANY | FM_SFD),
|
||||
(mount_config->use_one_fat ? 1 : 2),
|
||||
0,
|
||||
(sec_num <= 128 ? root_dir_entries : 0),
|
||||
alloc_unit_size
|
||||
};
|
||||
fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
|
||||
free(workbuf);
|
||||
ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mkfs failed (%d)", fresult);
|
||||
|
||||
if (out_flags) {
|
||||
*out_flags |= FORMATTED_DURING_LAST_MOUNT;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Mounting again");
|
||||
fresult = f_mount(fs, drv, 1);
|
||||
ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mount failed after formatting (%d)", fresult);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_bdl_mount(const char *base_path,
|
||||
esp_blockdev_handle_t bdl_handle,
|
||||
const esp_vfs_fat_mount_config_t *mount_config)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
vfs_fat_bdl_ctx_t *ctx = NULL;
|
||||
|
||||
ESP_RETURN_ON_FALSE(base_path, ESP_ERR_INVALID_ARG, TAG, "base_path is NULL");
|
||||
ESP_RETURN_ON_FALSE(bdl_handle != ESP_BLOCKDEV_HANDLE_INVALID, ESP_ERR_INVALID_ARG, TAG, "invalid BDL handle");
|
||||
ESP_RETURN_ON_FALSE(mount_config, ESP_ERR_INVALID_ARG, TAG, "mount_config is NULL");
|
||||
|
||||
BYTE pdrv = 0xFF;
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
|
||||
ESP_GOTO_ON_ERROR(ff_diskio_register_bdl(pdrv, bdl_handle), fail, TAG,
|
||||
"ff_diskio_register_bdl failed pdrv=%i, error - 0x(%x)", pdrv, ret);
|
||||
|
||||
FATFS *fs;
|
||||
esp_vfs_fat_conf_t conf = {
|
||||
.base_path = base_path,
|
||||
.fat_drive = drv,
|
||||
.max_files = mount_config->max_files,
|
||||
};
|
||||
ret = esp_vfs_fat_register(&conf, &fs);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
// already registered with VFS
|
||||
} else if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
WORD sec_size_w;
|
||||
if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &sec_size_w) != RES_OK) {
|
||||
ESP_LOGE(TAG, "failed to query sector size from diskio");
|
||||
ret = ESP_FAIL;
|
||||
goto fail;
|
||||
}
|
||||
size_t sec_size = (size_t)sec_size_w;
|
||||
size_t sec_num = (size_t)(bdl_handle->geometry.disk_size / sec_size);
|
||||
|
||||
if (bdl_handle->device_flags.read_only) {
|
||||
FRESULT fresult = f_mount(fs, drv, 1);
|
||||
if (fresult != FR_OK) {
|
||||
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
|
||||
ret = ESP_FAIL;
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
vfs_fat_x_ctx_flags_t flags = 0;
|
||||
ret = try_mount_rw(fs, drv, mount_config, &flags, sec_num, sec_size);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ctx = calloc(1, sizeof(vfs_fat_bdl_ctx_t));
|
||||
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, fail, TAG, "no mem");
|
||||
ctx->bdl_handle = bdl_handle;
|
||||
ctx->pdrv = pdrv;
|
||||
ctx->fs = fs;
|
||||
memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
|
||||
|
||||
uint32_t ctx_id = get_unused_ctx_id();
|
||||
assert(ctx_id != FF_VOLUMES);
|
||||
s_bdl_ctx[ctx_id] = ctx;
|
||||
|
||||
if (bdl_handle->device_flags.read_only) {
|
||||
esp_vfs_set_readonly_flag(base_path);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
f_mount(0, drv, 0);
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
ff_diskio_unregister(pdrv);
|
||||
free(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_bdl_unmount(const char *base_path, esp_blockdev_handle_t bdl_handle)
|
||||
{
|
||||
BYTE pdrv = ff_diskio_get_pdrv_bdl(bdl_handle);
|
||||
ESP_RETURN_ON_FALSE(pdrv != 0xff, ESP_ERR_INVALID_STATE, TAG,
|
||||
"BDL device isn't registered, call esp_vfs_fat_bdl_mount first");
|
||||
|
||||
uint32_t id = FF_VOLUMES;
|
||||
ESP_RETURN_ON_FALSE(get_ctx_id_by_bdl(bdl_handle, &id), ESP_ERR_INVALID_STATE, TAG,
|
||||
"BDL device isn't registered, call esp_vfs_fat_bdl_mount first");
|
||||
assert(id != FF_VOLUMES);
|
||||
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
ff_diskio_unregister(pdrv);
|
||||
ff_diskio_clear_pdrv_bdl(bdl_handle);
|
||||
|
||||
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
|
||||
|
||||
free(s_bdl_ctx[id]);
|
||||
s_bdl_ctx[id] = NULL;
|
||||
|
||||
return err;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_blockdev.h"
|
||||
#ifndef CONFIG_IDF_TARGET_LINUX
|
||||
#include "sdmmc_cmd.h"
|
||||
#endif
|
||||
@@ -36,6 +37,13 @@ static inline size_t esp_vfs_fat_get_allocation_unit_size(
|
||||
return alloc_unit_size;
|
||||
}
|
||||
|
||||
typedef struct vfs_fat_bdl_ctx_t {
|
||||
esp_blockdev_handle_t bdl_handle; //BDL device handle
|
||||
BYTE pdrv; //Drive number that is mounted
|
||||
FATFS *fs; //FAT structure pointer that is registered
|
||||
esp_vfs_fat_mount_config_t mount_config; //Mount configuration
|
||||
} vfs_fat_bdl_ctx_t;
|
||||
|
||||
#ifndef CONFIG_IDF_TARGET_LINUX
|
||||
typedef struct vfs_fat_sd_ctx_t {
|
||||
BYTE pdrv; //Drive number that is mounted
|
||||
|
||||
@@ -87,6 +87,8 @@ Examples
|
||||
- Demonstrates the capabilities of Python-based tooling for FATFS images available on host computers.
|
||||
* - :example:`ext_flash_fatfs <storage/fatfs/ext_flash>`
|
||||
- Demonstrates using FATFS over wear leveling on external flash.
|
||||
* - :example:`bdl_wl <storage/fatfs/bdl_wl>`
|
||||
- Demonstrates using FATFS over BDL wear-levelling stack on internal flash.
|
||||
* - :example:`wear_leveling <storage/wear_levelling>`
|
||||
- Demonstrates using FATFS over wear leveling on internal flash.
|
||||
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
- 演示了在主机上使用 Python 工具生成 FATFS 镜像的相关功能。
|
||||
* - :example:`ext_flash_fatfs <storage/fatfs/ext_flash>`
|
||||
- 演示了在外部 flash 上使用带有磨损均衡功能的 FATFS。
|
||||
* - :example:`bdl_wl <storage/fatfs/bdl_wl>`
|
||||
- 演示了在内部 flash 上通过 BDL 磨损均衡堆栈使用 FATFS。
|
||||
* - :example:`wear_leveling <storage/wear_levelling>`
|
||||
- 演示了在内部 flash 上使用带有磨损均衡功能的 FATFS。
|
||||
|
||||
|
||||
@@ -9,6 +9,17 @@ examples/storage/fatfs:
|
||||
- if: IDF_TARGET != "esp32"
|
||||
reason: only one target needed
|
||||
|
||||
examples/storage/fatfs/bdl_wl:
|
||||
depends_components:
|
||||
- *common_components
|
||||
- esp_blockdev
|
||||
- fatfs
|
||||
- vfs
|
||||
- wear_leveling
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32"
|
||||
reason: only one target needed
|
||||
|
||||
examples/storage/fatfs/ext_flash:
|
||||
depends_components:
|
||||
- *common_components
|
||||
|
||||
7
examples/storage/fatfs/bdl_wl/CMakeLists.txt
Normal file
7
examples/storage/fatfs/bdl_wl/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(fatfs_bdl_wl)
|
||||
55
examples/storage/fatfs/bdl_wl/README.md
Normal file
55
examples/storage/fatfs/bdl_wl/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
| 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 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# FatFS over BDL (Block Device Layer) - Wear-Levelling Stack
|
||||
|
||||
This example demonstrates mounting a FAT filesystem using the Block Device Layer (BDL) interface
|
||||
instead of the legacy `wl_handle_t`-based API.
|
||||
|
||||
## BDL Stack
|
||||
|
||||
The BDL stack constructed in this example:
|
||||
|
||||
```
|
||||
FatFS (VFS + POSIX API)
|
||||
|
|
||||
diskio_bdl (generic BDL diskio adapter)
|
||||
|
|
||||
WL BDL (wear-levelling, via wl_get_blockdev())
|
||||
|
|
||||
Partition BDL (flash partition, via esp_partition_get_blockdev())
|
||||
|
|
||||
SPI Flash (physical storage)
|
||||
```
|
||||
|
||||
The key advantage of BDL is that **the same `diskio_bdl` adapter works with any BDL device**.
|
||||
You can swap the bottom of the stack (e.g., use `sdmmc_get_blockdev()` for an SD card) without
|
||||
changing the FatFS integration code.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Build and flash
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type `Ctrl-]`.)
|
||||
|
||||
## Example output
|
||||
|
||||
```
|
||||
I (321) example: Creating partition BDL for 'storage' partition
|
||||
I (331) example: Partition BDL: disk_size=1048576, erase_size=4096
|
||||
I (331) example: Creating WL BDL on top of partition BDL
|
||||
I (341) example: WL BDL: disk_size=...., erase_size=4096
|
||||
I (341) example: Mounting FAT filesystem via BDL
|
||||
I (741) example: Filesystem mounted
|
||||
I (741) example: Opening file
|
||||
I (841) example: File written
|
||||
I (841) example: Reading file
|
||||
I (841) example: Read from file: 'Hello from FatFS over BDL!'
|
||||
I (841) example: Unmounting FAT filesystem
|
||||
I (941) example: Releasing BDL devices
|
||||
I (941) example: Done
|
||||
```
|
||||
3
examples/storage/fatfs/bdl_wl/main/CMakeLists.txt
Normal file
3
examples/storage/fatfs/bdl_wl/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "fatfs_bdl_wl_main.c"
|
||||
PRIV_REQUIRES vfs fatfs esp_blockdev esp_partition wear_levelling
|
||||
INCLUDE_DIRS ".")
|
||||
133
examples/storage/fatfs/bdl_wl/main/fatfs_bdl_wl_main.c
Normal file
133
examples/storage/fatfs/bdl_wl/main/fatfs_bdl_wl_main.c
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* FatFS over BDL (Block Device Layer) - Wear-Levelling stack example
|
||||
*
|
||||
* Demonstrates building a BDL stack and mounting FatFS on top of it:
|
||||
*
|
||||
* +-----------+
|
||||
* | FatFS | <- file system (VFS + FatFS)
|
||||
* +-----------+
|
||||
* | diskio_bdl| <- FatFS diskio driver for BDL devices
|
||||
* +-----------+
|
||||
* | WL BDL | <- wear-levelling BDL layer (wl_get_blockdev)
|
||||
* +-----------+
|
||||
* | Part BDL | <- partition BDL layer (esp_partition_get_blockdev)
|
||||
* +-----------+
|
||||
* | SPI Flash | <- physical storage
|
||||
* +-----------+
|
||||
*
|
||||
* The BDL approach decouples FatFS from any specific storage driver.
|
||||
* The same diskio_bdl adapter works with any BDL-compatible bottom device:
|
||||
* - partition BDL (flash partition)
|
||||
* - sdmmc BDL (SD/eMMC card)
|
||||
* - memory BDL (RAM disk for testing)
|
||||
* - or any custom BDL implementation
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_blockdev.h"
|
||||
#include "wear_levelling.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
const char *base_path = "/spiflash";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 1: Build the BDL stack */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
ESP_LOGI(TAG, "Creating partition BDL for 'storage' partition");
|
||||
|
||||
esp_blockdev_handle_t part_bdl = NULL;
|
||||
ESP_ERROR_CHECK(esp_partition_get_blockdev(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT,
|
||||
"storage", &part_bdl));
|
||||
|
||||
ESP_LOGI(TAG, " Partition BDL: disk_size=%llu, erase_size=%u",
|
||||
(unsigned long long)part_bdl->geometry.disk_size,
|
||||
(unsigned)part_bdl->geometry.erase_size);
|
||||
|
||||
ESP_LOGI(TAG, "Creating WL BDL on top of partition BDL");
|
||||
|
||||
esp_blockdev_handle_t wl_bdl = NULL;
|
||||
ESP_ERROR_CHECK(wl_get_blockdev(part_bdl, &wl_bdl));
|
||||
|
||||
ESP_LOGI(TAG, " WL BDL: disk_size=%llu, erase_size=%u",
|
||||
(unsigned long long)wl_bdl->geometry.disk_size,
|
||||
(unsigned)wl_bdl->geometry.erase_size);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 2: Mount FatFS on the BDL device */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
ESP_LOGI(TAG, "Mounting FAT filesystem via BDL");
|
||||
|
||||
const esp_vfs_fat_mount_config_t mount_config = {
|
||||
.max_files = 4,
|
||||
.format_if_mount_failed = true,
|
||||
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
|
||||
.use_one_fat = false,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_vfs_fat_bdl_mount(base_path, wl_bdl, &mount_config));
|
||||
|
||||
ESP_LOGI(TAG, "Filesystem mounted");
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 3: Use POSIX file operations */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const char *filename = "/spiflash/example.txt";
|
||||
|
||||
ESP_LOGI(TAG, "Opening file");
|
||||
FILE *f = fopen(filename, "wb");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing");
|
||||
return;
|
||||
}
|
||||
fprintf(f, "Hello from FatFS over BDL!\n");
|
||||
fclose(f);
|
||||
ESP_LOGI(TAG, "File written");
|
||||
|
||||
ESP_LOGI(TAG, "Reading file");
|
||||
f = fopen(filename, "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading");
|
||||
return;
|
||||
}
|
||||
char line[128];
|
||||
fgets(line, sizeof(line), f);
|
||||
fclose(f);
|
||||
|
||||
char *pos = strchr(line, '\n');
|
||||
if (pos) {
|
||||
*pos = '\0';
|
||||
}
|
||||
ESP_LOGI(TAG, "Read from file: '%s'", line);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 4: Unmount and tear down the BDL stack */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
ESP_LOGI(TAG, "Unmounting FAT filesystem");
|
||||
ESP_ERROR_CHECK(esp_vfs_fat_bdl_unmount(base_path, wl_bdl));
|
||||
|
||||
ESP_LOGI(TAG, "Releasing BDL devices");
|
||||
wl_bdl->ops->release(wl_bdl);
|
||||
part_bdl->ops->release(part_bdl);
|
||||
|
||||
ESP_LOGI(TAG, "Done");
|
||||
}
|
||||
6
examples/storage/fatfs/bdl_wl/partitions_example.csv
Normal file
6
examples/storage/fatfs/bdl_wl/partitions_example.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 1M,
|
||||
|
18
examples/storage/fatfs/bdl_wl/pytest_fatfs_bdl_wl_example.py
Normal file
18
examples/storage/fatfs/bdl_wl/pytest_fatfs_bdl_wl_example.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR 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'], indirect=['target'])
|
||||
def test_examples_fatfs_bdl_wl(dut: Dut) -> None:
|
||||
dut.expect('example: Mounting FAT filesystem via BDL', timeout=90)
|
||||
dut.expect('example: Filesystem mounted', timeout=90)
|
||||
dut.expect('example: Opening file', timeout=90)
|
||||
dut.expect('example: File written', timeout=90)
|
||||
dut.expect('example: Reading file', timeout=90)
|
||||
dut.expect("example: Read from file: 'Hello from FatFS over BDL!'", timeout=90)
|
||||
dut.expect('example: Unmounting FAT filesystem', timeout=90)
|
||||
dut.expect('example: Done', timeout=90)
|
||||
4
examples/storage/fatfs/bdl_wl/sdkconfig.defaults
Normal file
4
examples/storage/fatfs/bdl_wl/sdkconfig.defaults
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
Reference in New Issue
Block a user