Merge branch 'feature/add_blockdev_docs' into 'master'

feat(storage/blockdev): Add documentation including contracts for operations

Closes IDF-12759

See merge request espressif/esp-idf!43156
This commit is contained in:
Tomas Rohlinek
2026-03-26 16:17:17 +01:00
7 changed files with 365 additions and 14 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ The BDL interface follows the Open-Closed Principle (open for extension, closed
The BDL interface structure is described by the following pseudo-code:
```
struct esp_blockdev_t {
typedef struct esp_blockdev {
//DEVICE FLAGS
esp_blockdev_flags_t device_flags
+33 -13
View File
@@ -40,7 +40,13 @@ extern "C" {
#define ESP_BLOCKDEV_CMD_MARK_DELETED ESP_BLOCKDEV_CMD_SYSTEM_BASE /*!< mark given range as invalid, data to be deleted/overwritten eventually [ esp_blockdev_cmd_arg_erase_t* ]*/
#define ESP_BLOCKDEV_CMD_ERASE_CONTENTS (ESP_BLOCKDEV_CMD_SYSTEM_BASE + 1) /*!< erase required range and set the contents to the device's default bit values [ esp_blockdev_cmd_arg_erase_t* ]*/
typedef struct esp_blockdev_cmd_arg_erase_t {
/**
* @brief Arguments for block device erase control commands
*
* Supplied to ESP_BLOCKDEV_CMD_ERASE_CONTENTS and similar ioctls to describe
* the erase window.
*/
typedef struct esp_blockdev_cmd_arg_erase {
uint64_t start_addr; /*!< IN - starting address of the disk space to erase/trim/discard/sanitize (in bytes), must be a multiple of erase block size */
size_t erase_len; /*!< IN - size of the area to erase/trim/discard/sanitize (in bytes), must be a multiple of erase block size */
} esp_blockdev_cmd_arg_erase_t;
@@ -52,17 +58,29 @@ typedef struct esp_blockdev_cmd_arg_erase_t {
*
* @note Convenience macros ESP_BLOCKDEV_FLAGS_<INST>_CONFIG_DEFAULT() provide the most common setup of usual ESP-IDF component equipped with BDL interface. They can be used as a starting point for own initializers.
*/
typedef union {
#if defined(__DOXYGEN__)
typedef struct esp_blockdev_flags {
uint32_t read_only: 1; /*!< no erase/write operations allowed */
uint32_t encrypted: 1; /*!< the device data is encrypted */
uint32_t erase_before_write: 1; /*!< erasing required before any write operation */
uint32_t and_type_write: 1; /*!< 0-bits can't be changed to 1-bits - NAND/NOR flash behavior */
uint32_t default_val_after_erase: 1; /*!< default bit value after erasing (0 or 1) */
uint32_t reserved: 27; /*!< Reserved for future blockdev flags */
uint32_t val; /*!< Raw bitfield view for bulk initialization */
} esp_blockdev_flags_t;
#else
typedef union esp_blockdev_flags {
struct {
uint32_t read_only: 1; /*!< no erase/write operations allowed */
uint32_t encrypted: 1; /*!< the device data is encrypted */
uint32_t erase_before_write: 1; /*!< erasing required before any write operation */
uint32_t and_type_write: 1; /*!< 0-bits can't be changed to 1-bits - NAND/NOR flash behavior */
uint32_t default_val_after_erase: 1; /*!< default bit value after erasing (0 or 1) */
uint32_t reserved: 27;
uint32_t reserved: 27; /*!< Reserved for future blockdev flags */
};
uint32_t val;
uint32_t val; /*!< Raw bitfield view for bulk initialization */
} esp_blockdev_flags_t;
#endif
#define ESP_BLOCKDEV_FLAGS_CONFIG_DEFAULT() { \
{ \
@@ -89,7 +107,7 @@ typedef union {
*
* Various block size parameters needed for proper R/W/E processing on given device.
*/
typedef struct esp_blockdev_geometry_t {
typedef struct esp_blockdev_geometry {
/** Size of the device disk space (in bytes).
* Mandatory parameter.
@@ -130,10 +148,12 @@ typedef struct esp_blockdev_geometry_t {
} esp_blockdev_geometry_t;
typedef struct esp_blockdev esp_blockdev_t;
/** Standard BDL access handle, the only public BDL instance identifier.
* Allocated and initialized by the device's class factory, optionally closed by the device release handler.
*/
typedef struct esp_blockdev_t* esp_blockdev_handle_t;
typedef esp_blockdev_t* esp_blockdev_handle_t;
#define ESP_BLOCKDEV_HANDLE_INVALID NULL
/**
@@ -141,7 +161,7 @@ typedef struct esp_blockdev_t* esp_blockdev_handle_t;
*
* Various block operations needed for proper R/W/E processing on given device.
*/
typedef struct esp_blockdev_ops_t {
typedef struct esp_blockdev_ops {
/** READ operation:
* Read required number of bytes from the device at given offset, store the data into the output buffer.
@@ -213,16 +233,16 @@ typedef struct esp_blockdev_ops_t {
* @note The device context pointer is used to store the device-specific context. It is not used by the BDL layer and is intended to be used by the device implementation.
* @note The ops pointer holds the address of the device operations structure which can be shared by multiple device instances of the same type (driver).
*/
typedef struct esp_blockdev_t {
struct esp_blockdev {
/* Device context pointer */
void* ctx;
void* ctx; /*!< Owner-provided private data */
esp_blockdev_flags_t device_flags;
esp_blockdev_geometry_t geometry;
const esp_blockdev_ops_t* ops;
esp_blockdev_flags_t device_flags; /*!< Capabilities and requirements */
esp_blockdev_geometry_t geometry; /*!< Operation granularity and capacity */
const esp_blockdev_ops_t* ops; /*!< Function table implementing the device */
} esp_blockdev_t;
};
#ifdef __cplusplus
} // extern "C"
+1
View File
@@ -82,6 +82,7 @@ INPUT = \
$(PROJECT_PATH)/components/esp_adc/include/esp_adc/adc_continuous.h \
$(PROJECT_PATH)/components/esp_adc/include/esp_adc/adc_oneshot.h \
$(PROJECT_PATH)/components/esp_app_format/include/esp_app_desc.h \
$(PROJECT_PATH)/components/esp_blockdev/include/esp_blockdev.h \
$(PROJECT_PATH)/components/esp_bootloader_format/include/esp_bootloader_desc.h \
$(PROJECT_PATH)/components/esp_common/include/esp_check.h \
$(PROJECT_PATH)/components/esp_common/include/esp_err.h \
+163
View File
@@ -0,0 +1,163 @@
Block Device Layer
==================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
The Block Device Layer (BDL) defines a C interface that lets storage-oriented components exchange data without custom-made adapters. Each block device exposes this interface in the form of an :cpp:type:`esp_blockdev_handle_t` handle giving access to device flags, geometry information, and a set of supported operations as defined in ``components/esp_blockdev/include/esp_blockdev.h``. Higher-level code inspects that metadata and may invoke the available callbacks to perform I/O operations.
The unified interface makes it possible to compose BDL stacks supporting storage use-cases by chaining multiple universal components. A driver provides a handle representing access to the physical device; a middleware component consumes this handle, augments behaviour (for example splits the space on the device or adds wear levelling capability), and exposes a new handle to the next layer. The topmost components of the chain like file-systems are pure device consumers. This model allows filesystems, middleware, and physical drivers to be mixed and matched as long as every layer honours the interface contracts described below.
.. blockdiag::
:caption: Example Block Device Layer Stack
:align: center
blockdiag blockdev-stack {
default_fontsize = 14;
node_height = 60;
orientation = portrait;
default_group_color = none;
nvs [label = "NVS\n(nvs_flash)"];
fatfs [label = "FATFS\n(fatfs)"];
littlefs [label = "LittleFS\n(esp_littlefs)"];
consumer [label = "Block device\nconsumers", shape = ellipse];
wl [label = "Wear Levelling\n(wear_levelling)"];
middleware_1 [label = "Block device\nmiddleware", shape = ellipse];
nvs_part [label = "NVS Partition\n(esp_partition)"];
littlefs_part [label = "LittleFS Partition\n(esp_partition)"];
fat_part [label = "FAT Partition\n(esp_partition)"];
middleware_2 [label = "Block device\nmiddleware", shape = ellipse];
spi [label = "Flash\n(spi_flash)"];
provider [label = "Block device\nprovider", shape = ellipse];
d1 [shape = none, width = 1, height = 1];
d2 [shape = none, width = 1, height = 1];
d3 [shape = none, width = 1, height = 1];
nvs -> nvs_part -> spi;
fatfs -> wl -> fat_part -> spi;
littlefs -> littlefs_part -> spi;
consumer -> middleware_1 -> middleware_2 -> provider;
group { orientation = landscape; fatfs; littlefs; nvs; }
group { orientation = landscape; wl; }
group { orientation = landscape; fat_part; littlefs_part; nvs_part; }
group { orientation = landscape; d3; spi; }
}
Using Block Devices
-------------------
Handles
^^^^^^^
Block devices are accessed through :cpp:type:`esp_blockdev_handle_t`. Handles are obtained from the owning component via the ``<component>_get_blockdev()`` convention and must be released with the matching ``<component>_release_blockdev()`` helper once the device is no longer needed. Treat handles as opaque: only use the public API in ``components/esp_blockdev/include/esp_blockdev.h``, and do not move or modify the memory they reference.
Geometry and Flags
^^^^^^^^^^^^^^^^^^
Each device publishes an :cpp:type:`esp_blockdev_geometry_t` structure that reports capacity together with minimum read, write, and erase granularities. Optional recommended sizes act as performance hints but must not replace alignment checks against the mandatory values. The accompanying :cpp:type:`esp_blockdev_flags_t` structure advertises properties such as read-only media, encryption, or erase-before-write requirements. Middleware can change apparent geometry size, but must verify upon creation that the underlying layer fits its requirements, and ensure that the underlying device will only be accessed correctly.
Operations
^^^^^^^^^^
The :cpp:type:`esp_blockdev_ops_t` structure defines callbacks for read, write, erase, sync, ioctl, and release. Before invoking a callback, callers must ensure that the given pointer is not ``NULL``; a ``NULL`` pointer indicates that the operation is not supported. Callers are responsible for validating alignment and bounds using the geometry data and for respecting flag-driven requirements such as issuing an erase before writing to NAND-like media.
Typical Flow
^^^^^^^^^^^^
1. Acquire a handle from a driver or middleware provider.
2. Inspect geometry and flags to determine required alignment, available capacity, and special handling.
3. Issue read, write, erase, and sync requests through the operation table that the provider exposes.
4. Forward the handle to higher components or release it once all operations complete.
Example
^^^^^^^
.. code-block:: c
esp_blockdev_handle_t dev = my_component_get_blockdev();
const esp_blockdev_geometry_t *geometry = dev->geometry;
if (dev->ops->read && (sizeof(buffer) % geometry->read_size) == 0) {
ESP_ERROR_CHECK(dev->ops->read(dev, buffer, sizeof(buffer), 0, sizeof(buffer)));
}
if (dev->ops->release) {
ESP_ERROR_CHECK(dev->ops->release(dev));
}
Contracts
---------
Flags (:cpp:type:`esp_blockdev_flags_t`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Flags are initialised once during device creation and must remain immutable for the lifetime of the handle.
* ``read_only`` requires write, erase, and mutating ioctl commands to fail with an error such as :c:macro:`ESP_ERR_INVALID_STATE`.
* ``encrypted`` signals that on-media data is encrypted; higher layers must not assume plaintext visibility or transparent mapping.
* ``erase_before_write`` tells callers that a successful write requires an erase of the target range beforehand. If multiple write operations are issued to the same range without an erase operation in-between, the behavior is undefined, but will likely result in data corruption.
* ``and_type_write`` signals NAND/NOR-style behavior: programming only clears bits (1→0) and effectively stores ``existing_bits & new_bits``. Bits that are already zero remain zero even if the write request supplies ones; erasing first is the only way to restore them.
* ``default_val_after_erase`` identifies whether erased regions read as ``0x00`` or ``0xFF`` so middleware can keep sentinel values consistent.
Geometry (:cpp:type:`esp_blockdev_geometry_t`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``disk_size`` is the total accessible capacity in bytes; operations must reject any request whose end offset exceeds this value.
* ``read_size``, ``write_size``, and ``erase_size`` are mandatory alignment units, in bytes; both offsets and lengths must align to the corresponding size before the operation runs.
* Recommended sizes improve throughput when callers honour them but cannot replace the minimum alignment checks; implementations must accept any request that respects the required granularity.
* When a user sees read-write and read-only variants of the same underlying device, the geometry must be identical aside from the ``read_only`` flag. In particular, ``read_size``, ``write_size``, and ``erase_size`` should match between the two variants; ``recommended_*`` values are expected to match and should differ only when there is a clear benefit to doing so (and should be documented in such cases).
Operations (:cpp:type:`esp_blockdev_ops_t`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``read(dev_handle, dst_buf, dst_buf_size, src_addr, data_read_len)``
* Behaviour: Upon success copies exactly ``data_read_len`` bytes into ``dst_buf``.
* Preconditions: ``dst_buf`` is valid, ``src_addr`` and ``data_read_len`` are aligned to ``read_size``, ``src_addr + data_read_len <= disk_size``, and ``data_read_len <= dst_buf_size``.
* Postcondition: Returns :c:macro:`ESP_OK` when the copy succeeds or propagates a relevant ``ESP_ERR_*`` code on failure.
``write(dev_handle, src_buf, dst_addr, data_write_len)``
* Preconditions: Device is not ``read_only``; ``src_buf`` spans at least ``data_write_len`` bytes; offset and length align to ``write_size`` and stay within ``disk_size``.
* Behaviour: When ``erase_before_write`` is set, callers must issue an ``erase`` beforehand. When ``and_type_write`` is set, the hardware applies a bitwise AND with the existing contents, so the stored result becomes ``old_value & new_value``; bits cleared by earlier writes stay cleared unless the range is erased first.
* Postcondition: Returns :c:macro:`ESP_OK` once the requested range has been accepted by the device (data may still reside in intermediate buffers until ``sync`` runs). Misaligned, out-of-range, or read-only attempts should surface :c:macro:`ESP_ERR_INVALID_ARG` or :c:macro:`ESP_ERR_INVALID_STATE`, and implementations must avoid leaving the range partially updated.
* Note: On devices with ``and_type_write`` writes depend on the existing contents, so reading the same (or overlapping) range immediately after a write may require a preceding ``sync`` to ensure that cached data reflects the fully merged value.
``erase(dev_handle, start_addr, erase_len)``
* Preconditions: Device permits erases; ``start_addr`` and ``erase_len`` align to ``erase_size``; the range remains inside ``disk_size``.
* Postcondition: The range reads back as ``default_val_after_erase`` on success. Misaligned or out-of-range requests should return :c:macro:`ESP_ERR_INVALID_ARG`; hardware failures are expected to bubble up using driver-specific ``ESP_ERR_*`` codes.
``sync(dev_handle)``
* Flushes pending writes. Devices that omit this callback operate with write-through semantics.
* Postcondition: All previously reported writes reach stable storage before :c:macro:`ESP_OK` is returned (this includes all underlying devices). Timeouts or transport issues should surface as :c:macro:`ESP_ERR_TIMEOUT` or another relevant ``ESP_ERR_*``.
``ioctl(dev_handle, cmd, args)``
* Command identifiers `0x000x7F` are reserved for ESP-IDF system use; `0x800xFF` are available for user-defined extensions.
* Each command defines its own payload layout; because ``args`` is a ``void *``, wrappers can only validate or reinterpret the buffer for commands they understand, and must otherwise treat the payload as opaque.
* Wrappers that cannot service a command should forward it unchanged to the next device in the stack when available; only the bottom device is expected to return :c:macro:`ESP_ERR_NOT_SUPPORTED` for unrecognised commands.
* When the stack contains non-transparent address mapping, forwarding commands that embed raw addresses is inherently unsafe: intermediate layers cannot translate opaque payloads, so behaviour is undefined and will typically fail. Such commands should either be blocked explicitly or documented as unsupported in stacked configurations.
``release(dev_handle)``
* Optional destructor that frees device resources. The function must be idempotent so that repeated calls either succeed or return a benign error like :c:macro:`ESP_ERR_INVALID_STATE`.
Error Handling
^^^^^^^^^^^^^^
Callbacks return :c:macro:`ESP_OK` on success and should propagate ``ESP_ERR_*`` codes unchanged to help callers diagnose failures. Middleware and applications are expected to propagate errors from underlying devices rather than masking them inside the stack. NULL function pointers should be treated as "operation not supported".
Validation
^^^^^^^^^^
Implementations should include tests that cover alignment checks, flag-driven behaviour (read-only, erase-before-write, NAND-style writes), and correct propagation of errors through stacked devices. Middleware that wraps lower handles must also verify that handle lifetime management remains consistent across the stack.
.. _blockdev-apis:
API Reference
-------------
.. include-build-file:: inc/esp_blockdev.inc
+2
View File
@@ -8,6 +8,7 @@ This section contains reference of the high-level storage APIs. They are based o
- :doc:`Partitions API <partition>` allow block based access to SPI flash according to the :doc:`/api-guides/partition-tables`.
- :doc:`Non-Volatile Storage library (NVS) <nvs_flash>` implements a fault-tolerant wear-levelled key-value storage in SPI NOR flash.
- :doc:`Virtual File System (VFS) <vfs>` library provides an interface for registration of file system drivers. SPIFFS, FAT and various other file system libraries are based on the VFS.
- :doc:`Block Device Layer <blockdev>` defines a common block-device abstraction so storage drivers, middleware, and filesystems can interoperate without bespoke adapters.
- :doc:`SPIFFS <spiffs>` is a wear-levelled file system optimized for SPI NOR flash, well suited for small partition sizes and low throughput
- :doc:`FAT <fatfs>` is a standard file system which can be used in SPI flash or on SD/MMC cards
- :doc:`Wear Levelling <wear-levelling>` library implements a flash translation layer (FTL) suitable for SPI NOR flash. It is used as a container for FAT partitions in flash.
@@ -33,6 +34,7 @@ For information about storage security, please refer to :doc:`Storage Security <
nvs_partition_parse.rst
sdmmc
partition
blockdev
spiffs
vfs
wear-levelling
@@ -0,0 +1,163 @@
块设备层
========
:link_to_translation:`en:[English]`
概述
----
块设备层 (BDL) 定义了一套 C 语言接口,使面向存储的组件可在无需专用适配器的情况下完成数据交换。每个块设备以 :cpp:type:`esp_blockdev_handle_t` 句柄的形式对外提供该接口,通过该句柄可访问设备标志、几何信息以及 ``components/esp_blockdev/include/esp_blockdev.h`` 中定义的一组受支持操作。上层代码可读取这些元数据,并调用可用的回调执行 I/O 操作。
统一的接口支持串联多个通用组件,灵活组合成可适配各类存储场景的 BDL 协议栈。驱动程序提供用于访问物理设备的句柄;中间件组件使用该句柄并扩展其能力(如划分设备空间、添加磨损均衡功能),同时向下一层暴露新的句柄。协议栈最顶层的组件(如文件系统)是纯设备使用者。只要每一层均遵循下文约定的接口规范,该模型即可实现文件系统、中间件与物理驱动的任意组合使用。
.. blockdiag::
:caption: 块设备层堆栈示例
:align: center
blockdiag blockdev-stack {
default_fontsize = 14;
node_height = 60;
orientation = portrait;
default_group_color = none;
nvs [label = "NVS\n(nvs_flash)"];
fatfs [label = "FATFS\n(fatfs)"];
littlefs [label = "LittleFS\n(esp_littlefs)"];
consumer [label = "Block device\nconsumers", shape = ellipse];
wl [label = "Wear Levelling\n(wear_levelling)"];
middleware_1 [label = "Block device\nmiddleware", shape = ellipse];
nvs_part [label = "NVS Partition\n(esp_partition)"];
littlefs_part [label = "LittleFS Partition\n(esp_partition)"];
fat_part [label = "FAT Partition\n(esp_partition)"];
middleware_2 [label = "Block device\nmiddleware", shape = ellipse];
spi [label = "Flash\n(spi_flash)"];
provider [label = "Block device\nprovider", shape = ellipse];
d1 [shape = none, width = 1, height = 1];
d2 [shape = none, width = 1, height = 1];
d3 [shape = none, width = 1, height = 1];
nvs -> nvs_part -> spi;
fatfs -> wl -> fat_part -> spi;
littlefs -> littlefs_part -> spi;
consumer -> middleware_1 -> middleware_2 -> provider;
group { orientation = landscape; fatfs; littlefs; nvs; }
group { orientation = landscape; wl; }
group { orientation = landscape; fat_part; littlefs_part; nvs_part; }
group { orientation = landscape; d3; spi; }
}
使用块设备
----------
句柄
^^^^
块设备通过 :cpp:type:`esp_blockdev_handle_t` 进行访问。可以从对应的组件获取该句柄,遵循 ``<component>_get_blockdev()`` 的命名约定;当设备不再使用时,必须调用对应的 ``<component>_release_blockdev()`` 辅助函数来释放该句柄。注意,应该将句柄视为黑盒对象:只能通过 ``components/esp_blockdev/include/esp_blockdev.h`` 中提供的公开 API 来使用,不要移动或修改其所引用的内存。
几何与标志
^^^^^^^^^^
每个设备会发布一个 :cpp:type:`esp_blockdev_geometry_t` 结构体,用于报告容量以及最小读、写、擦除粒度。可选的推荐大小可作为性能提示,但不能替代针对强制取值的对齐检查。配套的 :cpp:type:`esp_blockdev_flags_t` 结构体声明只读介质、加密或先擦后写等属性。中间件可以改变表观几何大小,但在创建时必须验证底层是否满足其要求,并确保后续对底层设备的访问始终符合其约束。
操作
^^^^
:cpp:type:`esp_blockdev_ops_t` 结构体定义读、写、擦除、同步、ioctl 与释放等回调。在调用回调之前,调用方必须确保对应函数指针非 ``NULL`` ``NULL`` 表示不支持该操作。调用方有责任根据几何数据校验对齐与边界,并遵守由标识位指定的约束要求(例如在类 NAND 介质上写之前先执行擦除)。
典型流程
^^^^^^^^
1. 从驱动程序或中间件提供商处获取一个句柄。
2. 检查几何结构和标志,以确定所需的对齐方式、可用容量和特殊处理方式。
3. 通过提供商公开的操作表发出读取、写入、擦除和同步请求。
4. 将句柄转发给上层的组件,或者在所有操作完成后释放该句柄。
示例
^^^^
.. code-block:: c
esp_blockdev_handle_t dev = my_component_get_blockdev();
const esp_blockdev_geometry_t *geometry = dev->geometry;
if (dev->ops->read && (sizeof(buffer) % geometry->read_size) == 0) {
ESP_ERROR_CHECK(dev->ops->read(dev, buffer, sizeof(buffer), 0, sizeof(buffer)));
}
if (dev->ops->release) {
ESP_ERROR_CHECK(dev->ops->release(dev));
}
约定
----
标志(:cpp:type:`esp_blockdev_flags_t`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* 标志在设备创建时初始化一次,且在句柄整个生命周期内必须保持不变。
* ``read_only`` 要求写、擦除及会改变状态的 ioctl 命令失败,并返回诸如 :c:macro:`ESP_ERR_INVALID_STATE` 的错误。
* ``encrypted`` 表示介质上的数据已加密;上层不得假定可见明文或透明映射。
* ``erase_before_write`` 告知调用方:成功写入前必须先擦除目标范围。若在未插入擦除操作的情况下对同一范围多次写入,行为未定义,但很可能导致数据损坏。
* ``and_type_write`` 表示 NAND/NOR 风格行为:编程仅将位清为 0(1→0),实际存储 ``existing_bits & new_bits``。已为 0 的位在写请求写入 1 时仍保持为 0;只有先擦除才能恢复为 1。
* ``default_val_after_erase`` 标识擦除后区域读回为 ``0x00`` 还是 ``0xFF``,以便中间件保持哨兵值一致。
几何参数 (:cpp:type:`esp_blockdev_geometry_t`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ``disk_size`` 为可访问的总容量(字节);任何结束偏移量超出该值的请求都必须被拒绝。
* ``read_size````write_size````erase_size`` 为强制对齐单位(字节);在执行操作前,偏移与长度都必须和相应的大小对齐。
* 调用方遵循推荐大小时可提高吞吐量,但不能替代最低对齐检查;实现方必须接受所有符合强制粒度要求的请求。
* 当用户看到同一底层设备的可读写与只读两种变体时,除 ``read_only`` 标志外,两者的几何参数必须相同。特别是 ``read_size````write_size````erase_size`` 应保持一致; ``recommended_*`` 也应该保持一致,除非有特别的需要(此类情况应在文档中说明)。
操作 (:cpp:type:`esp_blockdev_ops_t`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``read(dev_handle, dst_buf, dst_buf_size, src_addr, data_read_len)``
* 行为:成功时,将恰好 ``data_read_len`` 字节数据复制到 ``dst_buf``
* 前置条件: ``dst_buf`` 有效, ``src_addr````data_read_len````read_size`` 对齐, ``src_addr + data_read_len <= disk_size``;且 ``data_read_len <= dst_buf_size``
* 后置条件:复制成功则返回 :c:macro:`ESP_OK`,失败则透传相应的 ``ESP_ERR_*`` 错误码。
``write(dev_handle, src_buf, dst_addr, data_write_len)``
* 前置条件:设备非 ``read_only`` ``src_buf`` 至少覆盖 ``data_write_len`` 字节;偏移与长度按 ``write_size`` 对齐且在 ``disk_size`` 范围内。
* 行为:若设置了 ``erase_before_write``,调用方必须先执行 ``erase``。若设置了 ``and_type_write``,硬件会将新内容与现有内容进行按位与运算 (AND),存储结果变为 ``old_value & new_value``;除非先擦除该范围,否则先前写入操作清除的位将保持清除状态。
* 后置条件:设备接受请求的范围后,将返回 :c:macro:`ESP_OK` (在 ``sync`` 运行之前,数据可能仍驻留在中间缓冲区中)。对齐错误、超出范围或只读尝试应会显现 :c:macro:`ESP_ERR_INVALID_ARG`:c:macro:`ESP_ERR_INVALID_STATE`,且实现方必须避免使该范围处于部分更新状态。
* 说明:在具有 ``and_type_write`` 的设备上,写操作依赖于现有内容,因此在写之后立即读取相同(或重叠)范围时,可能需要先执行 ``sync``,以确保缓存数据反映完全合并后的值。
``erase(dev_handle, start_addr, erase_len)``
* 前置条件:设备允许擦除; ``start_addr````erase_len````erase_size`` 对齐;范围在 ``disk_size`` 内。
* 后置条件:成功时该范围读回为 ``default_val_after_erase``。未对齐或越界请求应返回 :c:macro:`ESP_ERR_INVALID_ARG`;硬件故障应通过驱动相关的 ``ESP_ERR_*`` 码向上传递。
``sync(dev_handle)``
* 刷新挂起的写入操作。省略此回调的设备将采用直写语义运行。
* 后置条件:在返回 :c:macro:`ESP_OK` 之前,所有先前已报告的写操作均到达稳定存储(包括所有底层设备)。超时或传输问题应表现为 :c:macro:`ESP_ERR_TIMEOUT` 或其他相关 ``ESP_ERR_*``
``ioctl(dev_handle, cmd, args)``
* 命令标识 ``0x000x7F`` 保留给 ESP-IDF 系统使用; `0x800xFF` 可供用户自定义扩展。
* 每个命令定义各自的载荷布局;由于 ``args`` 为 ``void *`` 类型,封装层只能对其理解的命令校验或重新解释缓冲区,否则必须将载荷视为不透明数据。
* 无法处理某命令的封装层应在合适时机将其原样转发给协议栈中的下一设备;仅最底层设备应对无法识别的命令返回 :c:macro:`ESP_ERR_NOT_SUPPORTED`
* 当协议栈存在非透明地址映射时,转发嵌入原始地址的命令本质上不安全:中间层无法翻译不透明载荷,因此行为未定义且通常会失败。此类命令应被显式拦截,或在堆叠配置中注明为不支持。
``release(dev_handle)``
* 可选的析构函数,用于释放设备资源。该函数必须具有幂等性,即确保重复调用时要么成功,要么返回诸如 :c:macro:`ESP_ERR_INVALID_STATE` 等无害错误。
错误处理
^^^^^^^^
回调在成功时返回 :c:macro:`ESP_OK`,并应原样透传 ``ESP_ERR_*`` 错误码以帮助调用方诊断错误。中间件与应用程序须透传底层设备的错误,而不是在协议栈内掩盖这些错误。 ``NULL`` 函数指针视为“不支持该操作”。
验证
^^^^
实现方应包含覆盖对齐检查、由标志驱动的行为(只读、先擦后写、NAND 风格写)以及错误在堆叠设备间正确透传等测试。封装较低层句柄的中间件还必须验证句柄生命周期管理在整个协议栈中保持一致。
.. _blockdev-apis:
API 参考
--------
.. include-build-file:: inc/esp_blockdev.inc
@@ -8,6 +8,7 @@
- :doc:`分区表 API <partition>` 基于 :doc:`/api-guides/partition-tables` ,允许以块为单位访问 SPI flash。
- :doc:`非易失性存储库 (NVS) <nvs_flash>` 在 SPI NOR flash 上实现了一个有容错性,和磨损均衡功能的键值对存储。
- :doc:`虚拟文件系统 (VFS) <vfs>` 库提供了一个用于注册文件系统驱动的接口。SPIFFS、FAT 以及多种其他的文件系统库都基于 VFS。
- :doc:`块设备层 <blockdev>` 定义了一个通用的块设备抽象,使得存储驱动、中间件和文件系统可以互操作,而不需要专门的适配器。
- :doc:`SPIFFS <spiffs>` 是一个专为 SPI NOR flash 优化的磨损均衡的文件系统,非常适用于小分区和低吞吐率的应用。
- :doc:`FAT <fatfs>` 是一个可用于 SPI flash 或者 SD/MMC 存储卡的标准文件系统。
- :doc:`磨损均衡 <wear-levelling>` 库实现了一个适用于 SPI NOR flash 的 flash 翻译层 (FTL),用于 flash 中 FAT 分区的容器。
@@ -33,6 +34,7 @@
nvs_partition_parse.rst
sdmmc
partition
blockdev
spiffs
vfs
wear-levelling