diff --git a/dts/bindings/misc/zephyr,flash-no-erase.yaml b/dts/bindings/misc/zephyr,flash-no-erase.yaml new file mode 100644 index 000000000000000..9f81cea8b84dc4d --- /dev/null +++ b/dts/bindings/misc/zephyr,flash-no-erase.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Laczen +# SPDX-License-Identifier: Apache-2.0 + +description: Compatible describing that a flash device has no erase. + +compatible: "zephyr,flash-no-erase" diff --git a/dts/bindings/misc/zephyr,flash-partitions.yaml b/dts/bindings/misc/zephyr,flash-partitions.yaml new file mode 100644 index 000000000000000..6462f8232b01777 --- /dev/null +++ b/dts/bindings/misc/zephyr,flash-partitions.yaml @@ -0,0 +1,99 @@ +description: | + This binding is used to describe fixed partitions of a flash memory. + + Here is an example: + + &flash0 { + partitions { + compatible = "zephyr,flash-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 0x0000C000>; + }; + slot0_partition: partition@c000 { + label = "image-0"; + reg = <0x0000C000 0x00076000>; + }; + slot1_partition: partition@82000 { + label = "image-1"; + reg = <0x00082000 0x00076000>; + }; + + /* + * The flash starting at 0x000f8000 and ending at + * 0x000fffff is reserved for use by the application. + */ + + /* + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@f8000 { + label = "storage"; + reg = <0x000f8000 0x00008000>; + }; + }; + }; + + Note that the usual name for this node is 'partitions'. + The zephyr,flash-partitions node should be a child of the flash + memory node. Note also that the flash memory node is usually + different from the node representing the flash controller + IP block. + + Above, slot0_partition's register address 0xc000 means that + the partition begins at that offset from the parent flash + memory flash0's base address. That is, partition addresses + are relative; physical addresses must be calculated by adding + the start address of flash0 in memory to each partition's + reg address. + +compatible: "zephyr,flash-partitions" + +properties: + "#address-cells": + type: int + description: | + Number of cells required to represent a child node's + reg property address. This must be large enough to + represent the start offset of each partition. + + "#size-cells": + type: int + description: | + Number of cells required to represent a child node's + reg property address. This must be large enough to + represent the size of each partition in bytes. + +child-binding: + description: | + Each child node of the zephyr,flash-partitions node represents + an individual flash partition. These should usually + look like this: + + partition_nodelabel: partition@START_OFFSET { + label = "human-readable-name"; + reg = <0xSTART_OFFSET 0xSIZE>; + }; + properties: + label: + type: string + description: | + Human readable string describing the flash partition. + read-only: + type: boolean + description: set this property if the partition is read-only + erase-block-size: + type: int + description: override value for the flash node erase-block-size. + reg: + type: array + description: | + This should be in the format , where OFFSET + is the offset of the flash partition relative to the base + address of the parent memory, and SIZE is the size of + the partition in bytes. + required: true diff --git a/include/zephyr/storage/flash_partitions.h b/include/zephyr/storage/flash_partitions.h new file mode 100644 index 000000000000000..56a7188991877e8 --- /dev/null +++ b/include/zephyr/storage/flash_partitions.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2024 Laczen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public API for flash partitions + */ + +#ifndef ZEPHYR_INCLUDE_FLASH_PARTITIONS_H_ +#define ZEPHYR_INCLUDE_FLASH_PARTITIONS_H_ + +/** + * @brief Abstraction over flash memory and its partitions + * + * @defgroup flash_partitions_api flash_partitions interface + * @{ + */ + +/* + * This API makes it possible to operate on flash partitions easily and + * effectively. + */ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The flash_partitions subsystem provide an abstraction layer between flash + * memory specific drivers and higher-level applications for flash memory + * partitions. On flash partitions the following routines for operation are + * provided: open, read, write, erase, clear and close. + * + */ + +/** + * @brief Retrieve a pointer to the flash partition info struct. + * + * A flash partition with nodelabel "nodelabel" is retrieved as: + * const struct flash_partition *fp = FLASH_PARTITION_GET(nodelabel) + * + * @param nodelabel of the flash partition. + */ +#define FLASH_PARTITION_GET(nodelabel) \ + &UTIL_CAT(flash_partition_, DT_NODELABEL(nodelabel)) + +/** + * @brief flash_partition represents a flash partition. + */ +struct flash_partition { + /** flash device */ + const struct device *fldev; + /** flash partition size */ + const size_t size; + /** flash partition offset on flash device */ + const size_t offset; + /** flash partition erase-block-size */ + const size_t erase_block_size; + /** flash partition routines */ + int (*const open)(const struct flash_partition *partition); + int (*const read)(const struct flash_partition *partition, size_t start, + void *data, size_t len); + int (*const write)(const struct flash_partition *partition, size_t start, + const void *data, size_t len); + int (*const erase)(const struct flash_partition *partition, size_t start, + size_t len); + int (*const close)(const struct flash_partition *partition); +}; + +/** + * @brief Get the size in byte of a flash partition. + * + * @param[in] partition flash partition + * @return the size. + */ +size_t flash_partition_get_size(const struct flash_partition *partition); + +/** + * @brief Get the erase-block-size in byte of a flash partition. + * + * @param[in] partition flash partition + * @return the size. + */ +size_t flash_partition_get_ebs(const struct flash_partition *partition); + +/** + * @brief Open a flash partition. + * + * @param[in] partition flash partition + * + * @return 0 on success, negative errno code on fail. + */ +int flash_partition_open(const struct flash_partition *partition); + +/** + * @brief Read data from flash partition. Read boundaries are verified before + * read request is executed. + * + * + * @param[in] partition flash partition + * @param[in] start point relative from beginning of flash partition + * @param[out] data Buffer to store read data + * @param[in] len Size to read + * + * @return 0 on success, negative errno code on fail. + */ +int flash_partition_read(const struct flash_partition *partition, size_t start, + void *data, size_t len); + +/** + * @brief Write data to flash partition. Write boundaries are verified before + * write request is executed. + * + * @param[in] partition flash partition + * @param[in] start point relative from beginning of flash partition + * @param[out] data Buffer with data to be written + * @param[in] len Size to read + * + * @return 0 on success, negative errno code on fail. + */ +int flash_partition_write(const struct flash_partition *partition, size_t start, + const void *data, size_t len); + +/** + * @brief Erase range on flash partition. Erase boundaries are verified before + * erase is executed. + * + * @param[in] partition flash partition + * @param[in] start point relative from beginning of flash partition + * @param[in] len Size to erase + * + * @return 0 on success, negative errno code on fail. + */ +int flash_partition_erase(const struct flash_partition *partition, size_t start, + size_t len); + +/** + * @brief Close a flash partition. + * + * @param[in] partition flash partition + * + * @return 0 on success, negative errno code on fail. + */ +int flash_partition_close(const struct flash_partition *partition); + +/** @cond INTERNAL_HIDDEN */ + +#define FLASH_PARTITION_DECLARE(n) \ + extern const struct flash_partition flash_partition_##n; + +#define FLASH_PARTITIONS_DECLARE(inst) \ + DT_FOREACH_CHILD_STATUS_OKAY(inst, FLASH_PARTITION_DECLARE) + +DT_FOREACH_STATUS_OKAY(zephyr_flash_partitions, FLASH_PARTITIONS_DECLARE) +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_FLASH_PARTITIONS_H_ */ diff --git a/subsys/storage/CMakeLists.txt b/subsys/storage/CMakeLists.txt index ed94d1920ba0b6b..0f15fdc72e5a493 100644 --- a/subsys/storage/CMakeLists.txt +++ b/subsys/storage/CMakeLists.txt @@ -1,4 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 add_subdirectory_ifdef(CONFIG_FLASH_MAP flash_map) +add_subdirectory_ifdef(CONFIG_FLASH_PARTITIONS flash_partitions) add_subdirectory_ifdef(CONFIG_STREAM_FLASH stream) diff --git a/subsys/storage/Kconfig b/subsys/storage/Kconfig index 6d78778449ce624..7ec7b768854225e 100644 --- a/subsys/storage/Kconfig +++ b/subsys/storage/Kconfig @@ -6,6 +6,7 @@ menu "Storage" source "subsys/storage/flash_map/Kconfig" +source "subsys/storage/flash_partitions/Kconfig" source "subsys/storage/stream/Kconfig" endmenu diff --git a/subsys/storage/flash_partitions/CMakeLists.txt b/subsys/storage/flash_partitions/CMakeLists.txt new file mode 100644 index 000000000000000..1abb81c63f38cb0 --- /dev/null +++ b/subsys/storage/flash_partitions/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(flash_partitions.c) diff --git a/subsys/storage/flash_partitions/Kconfig b/subsys/storage/flash_partitions/Kconfig new file mode 100644 index 000000000000000..38afe26fc79d5f0 --- /dev/null +++ b/subsys/storage/flash_partitions/Kconfig @@ -0,0 +1,26 @@ +# Copyright (c) 2024, Laczen +# SPDX-License-Identifier: Apache-2.0 + +menuconfig FLASH_PARTITIONS + bool "flash partitions support" + default y if DT_HAS_ZEPHYR_FLASH_PARTITIONS_ENABLED + select FLASH + help + Enables support for the flash partitions system, which enables using + unified routines to work with partitions defined on flash devices. + +if FLASH_PARTITIONS + +config FLASH_PARTITIONS_RUNTIME_VERIFY + bool "runtime verification of erase-block-size" + default n + select FLASH_PAGE_LAYOUT + help + Enable runtime verification that erase-block-size is a multiple of the + flash erase block size. + +module = FLASH_PARTITIONS +module-str = flash_partitions +source "subsys/logging/Kconfig.template.log_config" + +endif #FLASH_PARTITIONS diff --git a/subsys/storage/flash_partitions/flash_partitions.c b/subsys/storage/flash_partitions/flash_partitions.c new file mode 100644 index 000000000000000..533a1d3926bced1 --- /dev/null +++ b/subsys/storage/flash_partitions/flash_partitions.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024, Laczen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(flash_partitions, CONFIG_FLASH_PARTITIONS_LOG_LEVEL); + +size_t flash_partition_get_size(const struct flash_partition *partition) +{ + if (partition == NULL) { + return 0U; + } + + return partition->size; +} + +size_t flash_partition_get_ebs(const struct flash_partition *partition) +{ + if (partition == NULL) { + return 0U; + } + + return partition->erase_block_size; +} + +int flash_partition_open(const struct flash_partition *partition) +{ + if (partition == NULL) { + return -EINVAL; + } + + if (partition->open == NULL) { + return -ENOTSUP; + } + + return partition->open(partition); +} + +int flash_partition_read(const struct flash_partition *partition, size_t start, + void *data, size_t len) +{ + if (partition == NULL) { + return -EINVAL; + } + + if (partition->read == NULL) { + return -ENOTSUP; + } + + if ((start + len) > partition->size) { + return -EINVAL; + } + + return partition->read(partition, start, data, len); +} + +int flash_partition_write(const struct flash_partition *partition, size_t start, + const void *data, size_t len) +{ + if (partition == NULL) { + return -EINVAL; + } + + if (partition->write == NULL) { + return -ENOTSUP; + } + + if ((start + len) > partition->size) { + return -EINVAL; + } + + return partition->write(partition, start, data, len); +} + +int flash_partition_erase(const struct flash_partition *partition, size_t start, + size_t len) +{ + if (partition == NULL) { + return -EINVAL; + } + + if (partition->erase == NULL) { + return -ENOTSUP; + } + + if ((start + len) > partition->size) { + return -EINVAL; + } + + return partition->erase(partition, start, len); +} + +int flash_partition_close(const struct flash_partition *partition) +{ + if (partition == NULL) { + return -EINVAL; + } + + if (partition->close == NULL) { + return -ENOTSUP; + } + + return partition->close(partition); +} + +static int fp_open(const struct flash_partition *partition) +{ + int rc = 0; + + if (!device_is_ready(partition->fldev)) { + rc = -ENODEV; + } + + if (IS_ENABLED(CONFIG_FLASH_PARTITIONS_RUNTIME_VERIFY)) { + const size_t end = partition->offset + partition->size; + struct flash_pages_info page = { + .start_offset = (off_t)partition->offset, + }; + + while (page.start_offset < end) { + rc = flash_get_page_info_by_offs( + partition->fldev, page.start_offset, &page); + if (rc != 0) { + LOG_ERR("failed to get flash page info"); + break; + } + + if ((partition->erase_block_size % page.size) != 0U) { + LOG_ERR("erase-block-size configuration error"); + rc = -EINVAL; + break; + } + + page.start_offset += page.size; + } + } + + return rc; +} + +static int fp_read(const struct flash_partition *partition, size_t start, + void *data, size_t len) +{ + LOG_DBG("read %d byte at 0x%x", len, start); + start += partition->offset; + return flash_read(partition->fldev, (off_t)start, data, len); +} + +static int fp_write(const struct flash_partition *partition, size_t start, + const void *data, size_t len) +{ + LOG_DBG("write %d byte at 0x%x", len, start); + start += partition->offset; + return flash_write(partition->fldev, (off_t)start, data, len); +} + +static int fp_erase(const struct flash_partition *partition, size_t start, + size_t len) +{ + LOG_DBG("erase %d byte at 0x%x", len, start); + start += partition->offset; + return flash_erase(partition->fldev, (off_t)start, len); +} + +static int fp_close(const struct flash_partition *partition) +{ + return 0; +} + +#define FLASH_PARTITION_SIZE(inst) DT_REG_SIZE(inst) +#define FLASH_PARTITION_OFF(inst) DT_REG_ADDR(inst) +#define FLASH_PARTITION_GPDEV(inst) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(inst, soc_nv_flash), \ + (DEVICE_DT_GET(DT_PARENT(inst))), (DEVICE_DT_GET(inst))) +#define FLASH_PARTITION_DEV(inst) FLASH_PARTITION_GPDEV(DT_GPARENT(inst)) +#define FLASH_PARTITION_EBS(inst) \ + DT_PROP_OR(inst, erase_block_size, \ + (DT_PROP_OR(DT_GPARENT(inst), erase_block_size, \ + (FLASH_PARTITION_SIZE(inst))))) +#define FLASH_PARTITION_WRITE_DISABLE(inst) DT_PROP_OR(inst, read_only, false) +#define FLASH_PARTITION_ERASE_DISABLE(inst) \ + DT_PROP_OR(inst, read_only, false) || \ + DT_NODE_HAS_COMPAT(DT_GPARENT(inst), zephyr_flash_no_erase) + +#define FLASH_PARTITION_DEFINE(inst) \ + const struct flash_partition flash_partition_##inst = { \ + .fldev = FLASH_PARTITION_DEV(inst), \ + .size = FLASH_PARTITION_SIZE(inst), \ + .offset = FLASH_PARTITION_OFF(inst), \ + .erase_block_size = FLASH_PARTITION_EBS(inst), \ + .open = fp_open, \ + .read = fp_read, \ + .write = FLASH_PARTITION_WRITE_DISABLE(inst) ? NULL : fp_write, \ + .erase = FLASH_PARTITION_ERASE_DISABLE(inst) ? NULL : fp_erase, \ + .close = fp_close, \ + }; + +#define FLASH_PARTITIONS_DEFINE(inst) \ + DT_FOREACH_CHILD(inst, FLASH_PARTITION_DEFINE) + +DT_FOREACH_STATUS_OKAY(zephyr_flash_partitions, FLASH_PARTITIONS_DEFINE)