Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storage: Storage area #79665

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions include/zephyr/storage/storage_area/storage_area.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Copyright (c) 2024 Laczen
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @brief A storage area is a region on flash, eeprom, disk, ram, ... with
* fixed write-size and fixed erase-size.
*
* The storage area API is a subsystem that creates a unified method to work
* with flash, eeprom, ram, disks, files, ... for storage.
*
* A storage area is an area that has a number of constant sized erase blocks,
* and has constant write block size. The storage area does not necessarily
* inherit the limitations of the underlying storage device but rather defines
* a method of how the underlying storage device will be used (however it does
* not remove any limitations of the underlying storage device).
*
* A storage area can be defined with wrong properties (e.g. an erase-size
* smaller than the erase-block-size of the underlying storage device).
* Optionally the definition is checked by defining the Kconfig option
* `CONFIG_STORAGE_AREA_VERIFY` (default enabled for `DEBUG` builds).
*
* The subsystem is easy extendable to create custom (virtual) storage areas
* that consist of e.g. a combination of flash and ram, an encrypted storage
* area, ...
*
* There following methods are exposed:
* - storage_area_read(): read data,
* - storage_area_readv(): read data vector,
* - storage_area_write(): write data,
* - storage_area_writev(): write data vector,
* - storage_area_erase(): erase (in erase block addressing),
* - storage_area_ioctl(): used for e.g. getting xip addresses,
*
* A storage area is defined e.g. for a read-write area on flash:
* @code{.c}
* STORAGE_AREA_FLASH_RW_DEFINE(name, ...);
* struct storage_area *area = GET_STORAGE_AREA(name);
* @endcode
*
* For other storage devices (eeprom, ram, disk, ...) similar macros are
* defines, but they might differ slightly.
*
* The write_size, erase_size, ... are declarations of how the storage_area
* will be used The write_size is limited to a power of 2, erase_size should
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* will be used The write_size is limited to a power of 2, erase_size should
* will be used. The write_size is limited to a power of 2, erase_size should

* be a multiple of write_size and size should be a multiple of erase_size.
* The macro definitions STORAGE_AREA_XXX_DEFINE(...) checks these conditions.
*
* @defgroup storage_area Storage area
* @ingroup storage_apis
* @{
*/

#ifndef ZEPHYR_INCLUDE_STORAGE_STORAGE_AREA_H_
#define ZEPHYR_INCLUDE_STORAGE_STORAGE_AREA_H_

#include <stdint.h>
#include <stddef.h>
#include <zephyr/sys/util.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Allow the sa_off_t type to be redefined if needed */
#ifdef CONFIG_STORAGE_AREA_OFFSET_64BIT
typedef uint64_t sa_off_t;
#else
typedef uint32_t sa_off_t;
#endif

struct storage_area;

/** storage area input-output vector */
struct storage_area_iovec {
void *data; /**< pointer to data */
size_t len; /**< data length */
};

/** storage area properties masks */
enum storage_area_properties_mask {
/** full overwrite (e.g. ram, rram, ...) */
STORAGE_AREA_PROP_FOVRWRITE = BIT(0),
/** limited overwrite (e.g. nor flash without crc */
STORAGE_AREA_PROP_LOVRWRITE = BIT(1),
/** erase value is 0x00 */
STORAGE_AREA_PROP_ZEROERASE = BIT(2),
/** erase while writing on flash devices */
STORAGE_AREA_PROP_AUTOERASE = BIT(3),
};

/** storage area ioctl commands */
enum storage_area_ioctl_cmd {
STORAGE_AREA_IOCTL_NONE,
/** retrieve the storage area xip address */
STORAGE_AREA_IOCTL_XIPADDRESS,
};

/** storage area api */
struct storage_area_api {
int (*readv)(const struct storage_area *area, sa_off_t offset,
const struct storage_area_iovec *iovec, size_t iovcnt);
int (*writev)(const struct storage_area *area, sa_off_t offset,
const struct storage_area_iovec *iovec, size_t iovcnt);
int (*erase)(const struct storage_area *area, size_t sblk, size_t bcnt);
int (*ioctl)(const struct storage_area *area,
enum storage_area_ioctl_cmd cmd, void *data);
};

/** storage area */
struct storage_area {
const struct storage_area_api *api;
size_t write_size;
size_t erase_size;
size_t erase_blocks;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be rather overall device size here, which is even indicated by initialization of disk_storage_area, flash_storage_area, and eeprom_storage area which all have to populate this by division of size by erase_size.
Additionally, in code of disk and eeprom you have to put the size back by multiplying them back, and there is no invocation of STORAGE_AREA_ERASESIZE anywhere and direct usage of erase_blocks is only in validations and in flash erase, which means that there is more cases for direct use of size rather than erase_blocks, which can be calculated from true, or emulated, erase size and size.

Note also that #71270 is in path of making erase optional for drivers, i.e. returning -ENOTSUP, some drivers will no longer return pages layout nor hold information on device erase page. That benefits devices like MRAM in Ambiq or RRAM in Nordic, and many other devices where code not needed for these drivers to work and be removed and benefit users by reduction of code flash usage taken by vendor code to support these devices.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether this should be size or erase_blocks should be determined based on use. A typical bootloader will want to know the number or erase-blocks to find out how much space is required for the update status and to walk over while copying/moving. By using the number of erase-blocks the application code might be increased a little, but at the advantage of reduced bootloader size.

/** bitfield of properties */
uint32_t props;
};

#define STORAGE_AREA_HAS_PROPERTY(area, prop) ((area->props & prop) == prop)
#define STORAGE_AREA_WRITESIZE(area) area->write_size
#define STORAGE_AREA_ERASESIZE(area) area->erase_size
#define STORAGE_AREA_SIZE(area) \
(area->erase_size * area->erase_blocks)
#define STORAGE_AREA_FOVRWRITE(area) \
STORAGE_AREA_HAS_PROPERTY(area, STORAGE_AREA_PROP_FOVRWRITE)
#define STORAGE_AREA_LOVRWRITE(area) \
STORAGE_AREA_HAS_PROPERTY(area, STORAGE_AREA_PROP_LOVRWRITE)
#define STORAGE_AREA_ERASEVALUE(area) \
STORAGE_AREA_HAS_PROPERTY(area, STORAGE_AREA_PROP_ZEROERASE) ? 0x00 \
: 0xff
#define STORAGE_AREA_AUTOERASE(area) \
STORAGE_AREA_HAS_PROPERTY(area, STORAGE_AREA_PROP_AUTOERASE)

/**
* @brief retrieve a pointer to a storage area
*
* @param _name the name of the storage area
*/
#define GET_STORAGE_AREA(_name) \
(struct storage_area *)&_storage_area_##_name.area

/**
* @brief Read iovec from storage area.
*
* @param area storage area.
* @param offset offset in storage area (byte).
* @param iovec io vector for read.
* @param iovcnt iovec element count.
*
* @retval 0 on success else negative errno code.
*/
int storage_area_readv(const struct storage_area *area, sa_off_t offset,
const struct storage_area_iovec *iovec, size_t iovcnt);

/**
* @brief Read from storage area.
*
* @param area storage area.
* @param offset offset in storage area (byte).
* @param data data.
* @param len read size.
*
* @retval 0 on success else negative errno code.
*/
int storage_area_read(const struct storage_area *area, sa_off_t offset,
void *data, size_t len);

/**
* @brief Write iovec to storage area.
*
* @param area storage area.
* @param offset offset in storage area (byte).
* @param iovec io vector to write.
* @param iovcnt iovec element count.
*
* @retval 0 on success else negative errno code.
*/
int storage_area_writev(const struct storage_area *area, sa_off_t offset,
const struct storage_area_iovec *iovec, size_t iovcnt);

/**
* @brief Write data to storage area.
*
* @param area storage area.
* @param offset offset in storage area (byte).
* @param data data to program.
* @param len program size.
*
* @retval 0 on success else negative errno code.
*/
int storage_area_write(const struct storage_area *area, sa_off_t offset,
const void *data, size_t len);

/**
* @brief Erase storage area.
*
* @param area storage area.
* @param sblk start block
* @param bcnt number of blocks to erase.
*
* @retval 0 on success else negative errno code.
*/
int storage_area_erase(const struct storage_area *area, size_t sblk,
size_t bcnt);

/**
* @brief Storage area ioctl.
*
* @param area storage area.
* @param cmd ioctl command.
* @param data in/out data pointer.
*
* @retval 0 on success else negative errno code.
*/
int storage_area_ioctl(const struct storage_area *area,
enum storage_area_ioctl_cmd cmd, void *data);

/**
* @}
*/

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_STORAGE_STORAGE_AREA_H_ */
107 changes: 107 additions & 0 deletions include/zephyr/storage/storage_area/storage_area_disk.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2024 Laczen
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @brief A storage area on disk
* @defgroup storage_area_disk Storage area on disk
* @ingroup storage_area
* @{
*/

#ifndef ZEPHYR_INCLUDE_STORAGE_STORAGE_AREA_DISK_H_
#define ZEPHYR_INCLUDE_STORAGE_STORAGE_AREA_DISK_H_

#include <zephyr/storage/disk_access.h>
#include <zephyr/storage/storage_area/storage_area.h>

#ifdef __cplusplus
extern "C" {
#endif

struct storage_area_disk {
const struct storage_area area;
const uint32_t start;
const size_t ssize;
const char *name;
};

extern const struct storage_area_api storage_area_disk_rw_api;
extern const struct storage_area_api storage_area_disk_ro_api;

/**
* @brief Helper macro to create a storage area on top of a disk
*/
#define STORAGE_AREA_DISK(_dname, _start, _ssize, _ws, _es, _size, _props, \
_api) \
{ \
.area = \
{ \
.api = ((_ws == 0) || \
((_ws & (_ws - 1)) != 0) || \
((_es % _ws) != 0) || \
((_size % _es) != 0)) \
? NULL \
: _api, \
.write_size = _ws, \
.erase_size = _es, \
.erase_blocks = _size / _es, \
.props = _props | STORAGE_AREA_PROP_FOVRWRITE, \
}, \
.name = _dname, .start = _start, .ssize = _ssize, \
}

/**
* @brief Define a read-write storage area on top of a disk
*
* @param _name storage area name: used by GET_STORAGE_AREA(_name)
* @param _dname disk name
* @param _start start sector on disk
* @param _ws write-size (equal or multiple of disk sector size)
* @param _es erase-size (equal or multiple of disk sector size)
* @param _size storage area size
* @param _props storage area properties (see storage_area.h)
*/
#define STORAGE_AREA_DISK_RW_DEFINE(_name, _dname, _start, _ssize, _ws, _es, \
_size, _props) \
BUILD_ASSERT(_ws != 0, "Invalid write size"); \
BUILD_ASSERT((_ws & (_ws - 1)) == 0, "Invalid write size"); \
BUILD_ASSERT((_es % _ws) == 0, "Invalid erase size"); \
BUILD_ASSERT((_size % _ws) == 0, "Invalid size"); \
BUILD_ASSERT((_ssize % _ws) == 0, "Invalid sector size"); \
const struct storage_area_disk _storage_area_##_name = \
STORAGE_AREA_DISK(_dname, _start, _ssize, _ws, _es, _size, \
_props, &storage_area_disk_rw_api)

/**
* @brief Define a read-only storage area on top of a disk
*
* see @ref STORAGE_AREA_DISK_RW_DEFINE for parameters
*
* remark: the write-size and erase-size are not used in read-only, however
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make these remarks stand out in the generated documentation consider using @remark, @important or @note doxygen directives.

* to have a correct interpretation of the data that is used in
* read-only mode it is advised to keep them equal to the sizes that
* were used when creating the area.
*/
#define STORAGE_AREA_DISK_RO_DEFINE(_name, _dname, _start, _ssize, _ws, _es, \
_size, _props) \
BUILD_ASSERT(_ws != 0, "Invalid write size"); \
BUILD_ASSERT((_ws & (_ws - 1)) == 0, "Invalid write size"); \
BUILD_ASSERT((_es % _ws) == 0, "Invalid erase size"); \
BUILD_ASSERT((_size % _ws) == 0, "Invalid size"); \
BUILD_ASSERT((_ssize % _ws) == 0, "Invalid sector size"); \
const struct storage_area_disk _storage_area_##_name = \
STORAGE_AREA_DISK(_dname, _start, _ssize, _ws, _es, _size, \
_props, &storage_area_disk_ro_api)

/**
* @}
*/

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_STORAGE_STORAGE_AREA_DISK_H_ */
Loading
Loading