Block Device Layer
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 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.

Example Block Device Layer Stack
Using Block Devices
Handles
Block devices are accessed through 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 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 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 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
Acquire a handle from a driver or middleware provider.
Inspect geometry and flags to determine required alignment, available capacity, and special handling.
Issue read, write, erase, and sync requests through the operation table that the provider exposes.
Forward the handle to higher components or release it once all operations complete.
Example
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 (esp_blockdev_flags_t)
Flags are initialised once during device creation and must remain immutable for the lifetime of the handle.
read_onlyrequires write, erase, and mutating ioctl commands to fail with an error such asESP_ERR_INVALID_STATE.encryptedsignals that on-media data is encrypted; higher layers must not assume plaintext visibility or transparent mapping.erase_before_writetells 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_writesignals NAND/NOR-style behavior: programming only clears bits (1→0) and effectively storesexisting_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_eraseidentifies whether erased regions read as0x00or0xFFso middleware can keep sentinel values consistent.
Geometry (esp_blockdev_geometry_t)
disk_sizeis the total accessible capacity in bytes; operations must reject any request whose end offset exceeds this value.read_size,write_size, anderase_sizeare 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_onlyflag. In particular,read_size,write_size, anderase_sizeshould 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 (esp_blockdev_ops_t)
read(dev_handle, dst_buf, dst_buf_size, src_addr, data_read_len)Behaviour: Upon success copies exactly
data_read_lenbytes intodst_buf.Preconditions:
dst_bufis valid,src_addranddata_read_lenare aligned toread_size,src_addr + data_read_len <= disk_size, anddata_read_len <= dst_buf_size.Postcondition: Returns
ESP_OKwhen the copy succeeds or propagates a relevantESP_ERR_*code on failure.
write(dev_handle, src_buf, dst_addr, data_write_len)Preconditions: Device is not
read_only;src_bufspans at leastdata_write_lenbytes; offset and length align towrite_sizeand stay withindisk_size.Behaviour: When
erase_before_writeis set, callers must issue anerasebeforehand. Whenand_type_writeis set, the hardware applies a bitwise AND with the existing contents, so the stored result becomesold_value & new_value; bits cleared by earlier writes stay cleared unless the range is erased first.Postcondition: Returns
ESP_OKonce the requested range has been accepted by the device (data may still reside in intermediate buffers untilsyncruns). Misaligned, out-of-range, or read-only attempts should surfaceESP_ERR_INVALID_ARGorESP_ERR_INVALID_STATE, and implementations must avoid leaving the range partially updated.Note: On devices with
and_type_writewrites depend on the existing contents, so reading the same (or overlapping) range immediately after a write may require a precedingsyncto ensure that cached data reflects the fully merged value.
erase(dev_handle, start_addr, erase_len)Preconditions: Device permits erases;
start_addranderase_lenalign toerase_size; the range remains insidedisk_size.Postcondition: The range reads back as
default_val_after_eraseon success. Misaligned or out-of-range requests should returnESP_ERR_INVALID_ARG; hardware failures are expected to bubble up using driver-specificESP_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
ESP_OKis returned (this includes all underlying devices). Timeouts or transport issues should surface asESP_ERR_TIMEOUTor another relevantESP_ERR_*.
ioctl(dev_handle, cmd, args)Command identifiers 0x00–0x7F are reserved for ESP-IDF system use; 0x80–0xFF are available for user-defined extensions.
Each command defines its own payload layout; because
argsis avoid *, 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
ESP_ERR_NOT_SUPPORTEDfor 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
ESP_ERR_INVALID_STATE.
Error Handling
Callbacks return 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.
API Reference
Header File
This header file can be included with:
#include "esp_blockdev.h"
This header file is a part of the API provided by the
esp_blockdevcomponent. To declare that your component depends onesp_blockdev, add the following to your CMakeLists.txt:REQUIRES esp_blockdev
or
PRIV_REQUIRES esp_blockdev
Structures
-
struct esp_blockdev_cmd_arg_erase
Arguments for block device erase control commands.
Supplied to ESP_BLOCKDEV_CMD_ERASE_CONTENTS and similar ioctls to describe the erase window.
-
struct esp_blockdev_flags
Block device property flags.
Various properties and characteristics of given BDL device.
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.
Public Members
-
uint32_t read_only
no erase/write operations allowed
-
uint32_t encrypted
the device data is encrypted
-
uint32_t erase_before_write
erasing required before any write operation
-
uint32_t and_type_write
0-bits can't be changed to 1-bits - NAND/NOR flash behavior
-
uint32_t default_val_after_erase
default bit value after erasing (0 or 1)
-
uint32_t reserved
Reserved for future blockdev flags
-
uint32_t val
Raw bitfield view for bulk initialization
-
uint32_t read_only
-
struct esp_blockdev_geometry
Block device geometry.
Various block size parameters needed for proper R/W/E processing on given device.
Public Members
-
uint64_t disk_size
Size of the device disk space (in bytes). Mandatory parameter.
-
size_t read_size
Minimum block size (in bytes) for disk-read operations on given device. Mandatory parameter.
-
size_t write_size
Minimum block size (in bytes) for disk-write operations on given device. Mandatory parameter for all R/W devices, 0 for R/O.
-
size_t erase_size
Minimum block size (in bytes) for erase operations on given device. Mandatory parameter for all R/W devices, 0 for R/O.
Note
Typically used as a sector size in file system meaning.
-
size_t recommended_write_size
Default write block size (in bytes) of given device. Recommended for optimal performance. 0 means not used.
-
size_t recommended_read_size
Default read block size (in bytes) of given device. Recommended for optimal performance. 0 means not used.
-
size_t recommended_erase_size
Default erase block size (in bytes) of given device. Recommended for optimal performance. 0 means not used.
Note
Typically used as target hardware erase block size.
-
uint64_t disk_size
-
struct esp_blockdev_ops
Block device operations.
Various block operations needed for proper R/W/E processing on given device.
Public Members
-
esp_err_t (*read)(esp_blockdev_handle_t dev_handle, uint8_t *dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len)
READ operation: Read required number of bytes from the device at given offset, store the data into the output buffer.
- Param dev_handle:
Target device handle
- Param dst_buf:
Output buffer to receive the data read
- Param dst_buf_size:
Size of the destination buffer (in bytes)
- Param src_addr:
Data read source address (offset in given device address space)
- Param data_read_len:
Data read length (in bytes)
-
esp_err_t (*write)(esp_blockdev_handle_t dev_handle, const uint8_t *src_buf, uint64_t dst_addr, size_t data_write_len)
WRITE operation: Write required number of bytes taken from the input memory buffer to the device at given offset.
- Param dev_handle:
Target device handle
- Param src_buf:
Input buffer providing the data to write
- Param dst_addr:
Data destination address (offset in given device address space)
- Param data_write_len:
Length of the data to be written (in bytes)
-
esp_err_t (*erase)(esp_blockdev_handle_t dev_handle, uint64_t start_addr, size_t erase_len)
ERASE operation: Erase given address range <start_addr, start_addr + erase_len>. Both erase_len and start_addr must be aligned to multiples of the erase block size (in bytes) given by 'erase_size' member. The erasing process can be secured by one or more operations, depending on given BDL device nature. The only expected result is safely wiped data within the required range.
- Param dev_handle:
Target device handle
- Param start_addr:
Start address for the erase operation
- Param erase_len:
Length of the address space chunk to be erased (in bytes)
-
esp_err_t (*sync)(esp_blockdev_handle_t dev_handle)
SYNC operation: Commit all the pending write operations and block until all are finished. Does nothing if the device doesn't support write operations caching
- Param dev_handle:
Target device handle
-
esp_err_t (*ioctl)(esp_blockdev_handle_t dev_handle, const uint8_t cmd, void *args)
IOCTL operation: I/O control commands. Each command has corresponding in/out parameters which are to be verified within given ioctl handler. The commands can be extended by the device implementation within the following ranges:
0x00-0x7F: IDF device commands (system)
0x80-0xFF: user-defined commands See ESP_BLOCKDEV_CMD* macros.
- Param dev_handle:
Target device handle
- Param cmd:
Command ID
- Param args:
Command specific arguments
-
esp_err_t (*release)(esp_blockdev_handle_t dev_handle)
Instance destructor: Cleanup code for the specific device handle
- Param dev_handle:
Target device handle
-
esp_err_t (*read)(esp_blockdev_handle_t dev_handle, uint8_t *dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len)
-
struct esp_blockdev
IDF generic Block device interface structure This structure defines an interface for a generic block-device capable of common disk operations and providing properties important for the device's deployment into the target component stack.
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).
Public Members
-
void *ctx
Owner-provided private data
-
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
-
void *ctx
Macros
-
ESP_BLOCKDEV_CMD_SYSTEM_BASE
Block device I/O control commands.
The commands are grouped into two categories: system and user. The system commands are used for internal device management and are not expected to be used by the user (values 0-127). The user commands are used for user-specific operations and are expected to be defined by the user (values 128-255). System commands base value
-
ESP_BLOCKDEV_CMD_USER_BASE
User commands base value
-
ESP_BLOCKDEV_CMD_MARK_DELETED
mark given range as invalid, data to be deleted/overwritten eventually [ esp_blockdev_cmd_arg_erase_t* ]
-
ESP_BLOCKDEV_CMD_ERASE_CONTENTS
erase required range and set the contents to the device's default bit values [ esp_blockdev_cmd_arg_erase_t* ]
-
ESP_BLOCKDEV_FLAGS_CONFIG_DEFAULT()
-
ESP_BLOCKDEV_FLAGS_INST_CONFIG_DEFAULT(flags)
-
ESP_BLOCKDEV_HANDLE_INVALID
Type Definitions
-
typedef struct esp_blockdev_cmd_arg_erase esp_blockdev_cmd_arg_erase_t
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_flags esp_blockdev_flags_t
Block device property flags.
Various properties and characteristics of given BDL device.
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 struct esp_blockdev_geometry esp_blockdev_geometry_t
Block device geometry.
Various block size parameters needed for proper R/W/E processing on given device.
-
typedef struct esp_blockdev esp_blockdev_t
-
typedef esp_blockdev_t *esp_blockdev_handle_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_ops esp_blockdev_ops_t
Block device operations.
Various block operations needed for proper R/W/E processing on given device.