diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 621cb9ea394df..0715282f34c7f 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -5766,6 +5766,16 @@ West: labels: - "area: CMSIS_6" +"West project: dhara": + status: maintained + maintainers: + - tpambor + files: + - drivers/disk/ftl_dhara.c + - modules/dhara/ + labels: + - "area: Disk Access" + "West project: edtt": status: maintained maintainers: diff --git a/drivers/disk/CMakeLists.txt b/drivers/disk/CMakeLists.txt index bf08530bdc422..f12c2ec67d042 100644 --- a/drivers/disk/CMakeLists.txt +++ b/drivers/disk/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory_ifdef(CONFIG_NVME nvme) # zephyr-keep-sorted-start zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_FLASH flashdisk.c) +zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_FTL ftl_dhara.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_LOOPBACK loopback_disk.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_RAM ramdisk.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_STM32 sdmmc_stm32.c) diff --git a/drivers/disk/Kconfig b/drivers/disk/Kconfig index 9521c7c97b487..c0d497a3db109 100644 --- a/drivers/disk/Kconfig +++ b/drivers/disk/Kconfig @@ -10,6 +10,7 @@ if DISK_DRIVERS # zephyr-keep-sorted-start source "drivers/disk/Kconfig.flash" +source "drivers/disk/Kconfig.ftl" source "drivers/disk/Kconfig.loopback" source "drivers/disk/Kconfig.mmc" source "drivers/disk/Kconfig.ram" diff --git a/drivers/disk/Kconfig.ftl b/drivers/disk/Kconfig.ftl new file mode 100644 index 0000000000000..9f78c83d4707d --- /dev/null +++ b/drivers/disk/Kconfig.ftl @@ -0,0 +1,35 @@ +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +config DISK_DRIVER_FTL + bool "Flash translation layer" + default y + depends on ZEPHYR_DHARA_MODULE + depends on DT_HAS_ZEPHYR_FTL_DHARA_ENABLED + select FLASH + select FLASH_MAP + select FLASH_EX_OP_ENABLED + help + Enable flash translation layer disk driver for NAND flashes. + +if DISK_DRIVER_FTL + +config DISK_FTL_GC_RATIO + int "Garbage collection ratio" + default 15 + help + This is the ratio of garbage collection operations to real writes when + automatic collection is active. Smaller values lead to faster and more + predictable input/output at the expense of capacity. + +config DISK_FTL_SUPPORT_CONCURRENT_ACCESS + bool "Support concurrent access" + help + Enable support for concurrent access to the disk. This allows multiple threads + to access the disk at the same time. + +module = DISK_FTL +module-str = disk_ftl +source "subsys/logging/Kconfig.template.log_config" + +endif # DISK_DRIVER_FTL diff --git a/drivers/disk/ftl_dhara.c b/drivers/disk/ftl_dhara.c new file mode 100644 index 0000000000000..4c11024dea711 --- /dev/null +++ b/drivers/disk/ftl_dhara.c @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_ftl_dhara + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(disk_ftl, CONFIG_DISK_FTL_LOG_LEVEL); + +struct disk_ftl_data { +#if CONFIG_DISK_FTL_SUPPORT_CONCURRENT_ACCESS + struct k_sem lock; +#endif + bool initialised; + struct disk_info info; + const struct flash_area *area; + size_t page_size; + size_t block_size; + size_t buffer_size; + uint8_t *page_buffer; + struct dhara_map dhara_map; + struct dhara_nand dhara_nand; + uint8_t *dhara_buffer; +}; + +int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + off_t block_addr = b * ctx->block_size + ctx->area->fa_off; + int block_status = FLASH_BLOCK_BAD; + int ret; + + ret = flash_ex_op(ctx->info.dev, FLASH_EX_OP_IS_BAD_BLOCK, (uintptr_t)&block_addr, + &block_status); + if (ret == -ENOTSUP) { + LOG_DBG("checking bad block is not supported"); + return 0; + } else if (ret != 0) { + LOG_ERR("checking bad block at 0x%08lx failed with error %d", (long)block_addr, + ret); + return 1; + } else if (block_status == FLASH_BLOCK_BAD) { + LOG_DBG("block at 0x%08lx is marked bad", (long)block_addr); + } else { + /* No more action if block is good */ + LOG_DBG("block at 0x%08lx is good", (long)block_addr); + } + + return (block_status == FLASH_BLOCK_BAD) ? 1 : 0; +} + +void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + off_t block_addr = b * ctx->block_size + ctx->area->fa_off; + int ret; + + ret = flash_ex_op(ctx->info.dev, FLASH_EX_OP_MARK_BAD_BLOCK, (uintptr_t)&block_addr, NULL); + if (ret == -ENOTSUP) { + LOG_DBG("marking bad block is not supported"); + } else if (ret != 0) { + LOG_ERR("marking bad block at 0x%08lx failed with error %d", (long)block_addr, ret); + } else { + LOG_DBG("marked block bad at 0x%08lx", (long)block_addr); + } +} + +int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + off_t block_addr = b * ctx->block_size + ctx->area->fa_off; + int ret; + + LOG_DBG("erasing block at 0x%08lx", (long)block_addr); + + ret = flash_erase(ctx->info.dev, block_addr, ctx->block_size); + if (ret != 0) { + LOG_ERR("erasing block at 0x%08lx failed with error %d", (long)block_addr, ret); + *err = DHARA_E_BAD_BLOCK; + return -1; + } + + return 0; +} + +int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, + dhara_error_t *err) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + off_t page_addr = p * ctx->page_size + ctx->area->fa_off; + int ret; + + LOG_DBG("writing page at 0x%08lx", (long)page_addr); + + ret = flash_write(ctx->info.dev, page_addr, data, ctx->page_size); + if (ret != 0) { + LOG_ERR("writing page at 0x%08lx failed with error %d", (long)page_addr, ret); + *err = DHARA_E_BAD_BLOCK; + return -1; + } + + return 0; +} + +int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + const struct flash_parameters *flash_params = flash_get_parameters(ctx->info.dev); + off_t page_addr = p * ctx->page_size + ctx->area->fa_off; + int ret; + + LOG_DBG("checking erase status of page at 0x%08lx", (long)page_addr); + + ret = flash_read(ctx->info.dev, page_addr, ctx->page_buffer, ctx->page_size); + if (ret != 0) { + LOG_ERR("reading page at 0x%08lx failed with error %d", (long)page_addr, ret); + return 0; + } + + if (ctx->page_buffer[0] != flash_params->erase_value) { + return 0; + } + + if (memcmp(ctx->page_buffer, ctx->page_buffer + 1, ctx->page_size - 1) != 0) { + return 0; + } + + return 1; +} + +int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, + uint8_t *data, dhara_error_t *err) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + off_t page_addr = p * ctx->page_size + ctx->area->fa_off; + int ret; + + LOG_DBG("reading page at 0x%08lx, offset 0x%zx, length 0x%zx", (long)page_addr, offset, + length); + + ret = flash_read(ctx->info.dev, page_addr + offset, data, length); + if (ret != 0) { + LOG_ERR("reading data at 0x%08lx failed with error %d", (long)(page_addr + offset), + ret); + *err = DHARA_E_ECC; + return -1; + } + + return 0; +} + +int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, + dhara_error_t *err) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(n, struct disk_ftl_data, dhara_nand); + off_t src_page_addr = src * ctx->page_size + ctx->area->fa_off; + off_t dst_page_addr = dst * ctx->page_size + ctx->area->fa_off; + int ret; + + LOG_DBG("copying page from 0x%08lx to 0x%08lx", (long)src_page_addr, (long)dst_page_addr); + + ret = flash_read(ctx->info.dev, src_page_addr, ctx->page_buffer, ctx->page_size); + if (ret != 0) { + LOG_ERR("reading page at 0x%08lx failed with error %d", (long)src_page_addr, ret); + *err = DHARA_E_ECC; + return -1; + } + + ret = flash_write(ctx->info.dev, dst_page_addr, ctx->page_buffer, ctx->page_size); + if (ret != 0) { + LOG_ERR("writing page at 0x%08lx failed with error %d", (long)dst_page_addr, ret); + *err = DHARA_E_BAD_BLOCK; + return -1; + } + + return 0; +} + +static __maybe_unused int flash_area_is_uniform(const struct flash_area *fa) +{ + int ret; + struct flash_pages_info page; + size_t block_size = 0; + off_t offset = fa->fa_off; + + while (offset < (fa->fa_off + fa->fa_size)) { + ret = flash_get_page_info_by_offs(fa->fa_dev, offset, &page); + if (ret != 0) { + return ret; + } + + if (block_size == 0) { + block_size = page.size; + } + + if (page.size != block_size) { + return -EINVAL; + } + + offset += page.size; + } + + return 0; +} + +/* Everything necessary to acquire owning access to the disk. */ +static void acquire_disk(__maybe_unused struct disk_info *disk) +{ +#if CONFIG_DISK_FTL_SUPPORT_CONCURRENT_ACCESS + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + + k_sem_take(&ctx->lock, K_FOREVER); +#endif +} + +/* Everything necessary to release access to the disk. */ +static void release_disk(__maybe_unused struct disk_info *disk) +{ +#if CONFIG_DISK_FTL_SUPPORT_CONCURRENT_ACCESS + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + + k_sem_give(&ctx->lock); +#endif +} + +int disk_ftl_access_init(struct disk_info *disk) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + const struct flash_parameters *flash_params; + struct flash_pages_info page; /* Information of the smallest erasable area */ + dhara_error_t err; + int ret; + + acquire_disk(disk); + + if (ctx->initialised) { + release_disk(disk); + LOG_ERR("FTL is already initialised"); + return -EALREADY; + } + + if (!flash_area_device_is_ready(ctx->area)) { + release_disk(disk); + LOG_ERR("Flash device %s is not ready", ctx->area->fa_dev->name); + return -ENODEV; + } + + ctx->info.dev = flash_area_get_device(ctx->area); + + if (ctx->info.dev == NULL) { + release_disk(disk); + LOG_ERR("Flash device was not found"); + return -ENODEV; + } + + flash_params = flash_get_parameters(disk->dev); + ctx->page_size = flash_params->write_block_size; + __ASSERT(ctx->buffer_size >= ctx->page_size, + "Buffer size %u is too small for pages with size %u", ctx->buffer_size, + ctx->page_size); + + ret = flash_get_page_info_by_offs(disk->dev, ctx->area->fa_off, &page); + if (ret != 0) { + release_disk(disk); + LOG_ERR("Getting flash page info at 0x%lX failed with error %d", ctx->area->fa_off, + ret); + return ret; + } + + ctx->block_size = page.size; + + __ASSERT(ctx->area->fa_off == page.start_offset, + "Partition does not start at beginning of an erase block"); + __ASSERT(ctx->area->fa_size % page.size == 0, + "Partition size is not a multiple of erase block size"); + __ASSERT(flash_area_is_uniform(ctx->area) == 0, + "Partition does not have uniform erase block size"); + + __ASSERT((ctx->page_size & (ctx->page_size - 1)) == 0, "page size is not a power of 2"); + __ASSERT((ctx->block_size & (ctx->block_size - 1)) == 0, + "erase block size is not a power of 2"); + + ctx->dhara_nand.log2_page_size = LOG2CEIL(ctx->page_size); + ctx->dhara_nand.log2_ppb = LOG2CEIL(ctx->block_size / ctx->page_size); + ctx->dhara_nand.num_blocks = ctx->area->fa_size / ctx->block_size; + + LOG_DBG("Initialise Dhara with log2_page_size=%u, log2_ppb=%u, num_blocks=%u", + ctx->dhara_nand.log2_page_size, ctx->dhara_nand.log2_ppb, + ctx->dhara_nand.num_blocks); + + dhara_map_init(&ctx->dhara_map, &ctx->dhara_nand, ctx->dhara_buffer, + CONFIG_DISK_FTL_GC_RATIO); + + ret = dhara_map_resume(&ctx->dhara_map, &err); + if (ret != 0) { + LOG_ERR("dhara_map_resume failed with error %d", err); + } + + ctx->initialised = true; + release_disk(disk); + + return 0; +} + +static int disk_ftl_access_status(struct disk_info *disk) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + int status = DISK_STATUS_OK; + + if (!disk->dev || !device_is_ready(disk->dev)) { + status |= DISK_STATUS_NOMEDIA; + } + + if (!ctx->initialised) { + status |= DISK_STATUS_UNINIT; + } + + return status; +} + +static int disk_ftl_access_read(struct disk_info *disk, uint8_t *data_buf, uint32_t start_sector, + uint32_t num_sector) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + uint32_t total_sectors; + dhara_error_t err; + int ret; + uint8_t *buffer = data_buf; + + acquire_disk(disk); + + if (!ctx->initialised) { + release_disk(disk); + return -EINVAL; + } + + total_sectors = dhara_map_capacity(&ctx->dhara_map); + + if ((total_sectors < num_sector) || (total_sectors - num_sector) < start_sector) { + release_disk(disk); + LOG_ERR("Requested sectors are out of range"); + return -EINVAL; + } + + for (uint32_t i = 0; i < num_sector; i++) { + uint32_t sector = start_sector + i; + + ret = dhara_map_read(&ctx->dhara_map, sector, buffer, &err); + if (ret != 0) { + release_disk(disk); + LOG_ERR("dhara_map_read failed with error %d", err); + return -EIO; + } + + buffer += ctx->page_size; + } + + release_disk(disk); + + return 0; +} + +static int disk_ftl_access_write(struct disk_info *disk, const uint8_t *data_buf, + uint32_t start_sector, uint32_t num_sector) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + uint32_t total_sectors; + dhara_error_t err; + int ret; + const uint8_t *buffer = data_buf; + + acquire_disk(disk); + + if (!ctx->initialised) { + release_disk(disk); + return -EINVAL; + } + + total_sectors = dhara_map_capacity(&ctx->dhara_map); + + if ((total_sectors < num_sector) || (total_sectors - num_sector) < start_sector) { + release_disk(disk); + LOG_ERR("Requested sectors are out of range"); + return -EINVAL; + } + + for (uint32_t i = 0; i < num_sector; i++) { + uint32_t sector = start_sector + i; + + ret = dhara_map_write(&ctx->dhara_map, sector, buffer, &err); + if (ret != 0) { + release_disk(disk); + LOG_ERR("dhara_map_write failed with error %d", err); + return -EIO; + } + + buffer += ctx->page_size; + } + + release_disk(disk); + + return 0; +} + +static int disk_ftl_access_erase(struct disk_info *disk, uint32_t start_sector, uint32_t num_sector) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + uint32_t total_sectors; + dhara_error_t err; + int ret; + + acquire_disk(disk); + + if (!ctx->initialised) { + release_disk(disk); + return -EINVAL; + } + + total_sectors = dhara_map_capacity(&ctx->dhara_map); + + if ((total_sectors < num_sector) || (total_sectors - num_sector) < start_sector) { + release_disk(disk); + LOG_ERR("Requested sectors are out of range"); + return -EINVAL; + } + + for (uint32_t i = 0; i < num_sector; i++) { + uint32_t sector = start_sector + i; + + ret = dhara_map_trim(&ctx->dhara_map, sector, &err); + if (ret != 0) { + release_disk(disk); + LOG_ERR("dhara_map_trim failed with error %d", err); + return -EIO; + } + } + + release_disk(disk); + + return 0; +} + +static int disk_ftl_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) +{ + struct disk_ftl_data *ctx = CONTAINER_OF(disk, struct disk_ftl_data, info); + dhara_error_t err; + int ret; + + switch (cmd) { + case DISK_IOCTL_GET_SECTOR_COUNT: + acquire_disk(disk); + if (!ctx->initialised) { + release_disk(disk); + return -EINVAL; + } + + *(uint32_t *)buff = dhara_map_capacity(&ctx->dhara_map); + release_disk(disk); + break; + + case DISK_IOCTL_GET_SECTOR_SIZE: + acquire_disk(disk); + if (!ctx->initialised) { + release_disk(disk); + return -EINVAL; + } + + *(uint32_t *)buff = ctx->page_size; + release_disk(disk); + break; + + case DISK_IOCTL_GET_ERASE_BLOCK_SZ: /* in sectors */ + *(uint32_t *)buff = 1; + break; + + case DISK_IOCTL_CTRL_SYNC: + acquire_disk(disk); + if (!ctx->initialised) { + release_disk(disk); + return -EINVAL; + } + + ret = dhara_map_sync(&ctx->dhara_map, &err); + release_disk(disk); + if (ret != 0) { + LOG_ERR("dhara_map_sync failed with error %d", err); + return -EIO; + } + break; + + case DISK_IOCTL_CTRL_INIT: + return disk_ftl_access_init(disk); + + case DISK_IOCTL_CTRL_DEINIT: + acquire_disk(disk); + if (!ctx->initialised) { + release_disk(disk); + break; + } + ret = dhara_map_sync(&ctx->dhara_map, &err); + if (ret != 0) { + release_disk(disk); + LOG_ERR("dhara_map_sync failed with error %d", err); + return -EIO; + } + + ctx->initialised = false; + release_disk(disk); + break; + + default: + LOG_ERR("Unsupported ioctl command %u", cmd); + return -ENOTSUP; + } + + return 0; +} + +static int disk_ftl_init(const struct device *dev) +{ + struct disk_ftl_data *dev_data = dev->data; + +#if CONFIG_DISK_FTL_SUPPORT_CONCURRENT_ACCESS + k_sem_init(&dev_data->lock, 1, 1); +#endif + + return disk_access_register(&dev_data->info); +} + +static const struct disk_operations disk_ftl_ops = { + .init = disk_ftl_access_init, + .status = disk_ftl_access_status, + .read = disk_ftl_access_read, + .write = disk_ftl_access_write, + .erase = disk_ftl_access_erase, + .ioctl = disk_ftl_access_ioctl, +}; + +#define PARTITION_PHANDLE(n) DT_PHANDLE(DT_DRV_INST(n), partition) + +#define DISK_FTL_INIT(n) \ + static uint8_t disk_ftl_page_buffer_##n[DT_INST_PROP(n, buffer_size)]; \ + \ + static uint8_t disk_ftl_dhara_buffer_##n[DT_INST_PROP(n, buffer_size)]; \ + \ + static struct disk_ftl_data disk_ftl_data_##n = { \ + .info = \ + { \ + .name = DT_INST_PROP(n, disk_name), \ + .ops = &disk_ftl_ops, \ + }, \ + .area = FIXED_PARTITION_BY_NODE(PARTITION_PHANDLE(n)), \ + .buffer_size = DT_INST_PROP(n, buffer_size), \ + .page_buffer = disk_ftl_page_buffer_##n, \ + .dhara_buffer = disk_ftl_dhara_buffer_##n, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, disk_ftl_init, NULL, &disk_ftl_data_##n, NULL, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL); + +DT_INST_FOREACH_STATUS_OKAY(DISK_FTL_INIT) diff --git a/drivers/flash/Kconfig.simulator b/drivers/flash/Kconfig.simulator index e86f7a9517742..738951fb53e00 100644 --- a/drivers/flash/Kconfig.simulator +++ b/drivers/flash/Kconfig.simulator @@ -102,4 +102,11 @@ config FLASH_SIMULATOR_STAT_PAGE_COUNT This is why it's not possible to calculate the number of pages with preprocessor using DT properties. +config FLASH_SIMULATOR_EX_OP + bool "Extended operations API" + select FLASH_HAS_EX_OP + help + The flash extended operations API can be used to perform non-standard operations. For example + to fake bad block management required for NAND flash translation layers. + endif # FLASH_SIMULATOR diff --git a/drivers/flash/flash_handlers.c b/drivers/flash/flash_handlers.c index fd809e8d43bd7..e22c91b8b352c 100644 --- a/drivers/flash/flash_handlers.c +++ b/drivers/flash/flash_handlers.c @@ -139,11 +139,24 @@ static inline int z_vrfy_flash_ex_op(const struct device *dev, uint16_t code, { K_OOPS(K_SYSCALL_DRIVER_FLASH(dev, ex_op)); - /* - * If the code is a vendor code, then ex_op function have to perform - * verification. Zephyr codes should be verified here, but currently - * there are no Zephyr extended codes yet. - */ + switch (code) { + case FLASH_EX_OP_RESET: + /* No input or output for this code, so no verification needed. */ + break; + case FLASH_EX_OP_IS_BAD_BLOCK: + K_OOPS(K_SYSCALL_MEMORY_READ(in, sizeof(off_t))); + K_OOPS(K_SYSCALL_MEMORY_WRITE(out, sizeof(enum flash_block_status))); + break; + case FLASH_EX_OP_MARK_BAD_BLOCK: + K_OOPS(K_SYSCALL_MEMORY_READ(in, sizeof(off_t))); + break; + default: + /* + * If the code is a vendor code, then ex_op function has to perform + * verification. + */ + break; + } return z_impl_flash_ex_op(dev, code, in, out); } diff --git a/drivers/flash/flash_simulator.c b/drivers/flash/flash_simulator.c index baf9ac93ee7b1..3f9cbed804729 100644 --- a/drivers/flash/flash_simulator.c +++ b/drivers/flash/flash_simulator.c @@ -402,6 +402,18 @@ flash_sim_get_parameters(const struct device *dev) return cfg->flash_parameters; } +#ifdef CONFIG_FLASH_EX_OP_ENABLED +int flash_sim_ex_op(const struct device *dev, uint16_t code, const uintptr_t in, void *out) +{ + ARG_UNUSED(dev); + ARG_UNUSED(code); + ARG_UNUSED(in); + ARG_UNUSED(out); + + return -ENOTSUP; +} +#endif /* CONFIG_FLASH_EX_OP_ENABLED */ + static DEVICE_API(flash, flash_sim_api) = { .read = flash_sim_read, .write = flash_sim_write, @@ -411,6 +423,9 @@ static DEVICE_API(flash, flash_sim_api) = { #ifdef CONFIG_FLASH_PAGE_LAYOUT .page_layout = flash_sim_page_layout, #endif +#ifdef CONFIG_FLASH_EX_OP_ENABLED + .ex_op = flash_sim_ex_op, +#endif /* CONFIG_FLASH_EX_OP_ENABLED */ }; #ifdef CONFIG_ARCH_POSIX diff --git a/dts/bindings/disk/zephyr,ftl-dhara.yaml b/dts/bindings/disk/zephyr,ftl-dhara.yaml new file mode 100644 index 0000000000000..01e1f0b7c907c --- /dev/null +++ b/dts/bindings/disk/zephyr,ftl-dhara.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +description: | + Flash translation layer disk driver with Dhara implementation on flash partition. + +compatible: "zephyr,ftl-dhara" + +properties: + partition: + type: phandle + required: true + description: | + Backing storage flash map partition. + + disk-name: + type: string + required: true + description: | + Disk name. + + buffer-size: + type: int + required: true + description: | + Buffer size in bytes for the FTL driver. Must be equal or greater than the page size of the + backing flash. diff --git a/include/zephyr/drivers/flash.h b/include/zephyr/drivers/flash.h index 91a8d199231bf..25e68020be318 100644 --- a/include/zephyr/drivers/flash.h +++ b/include/zephyr/drivers/flash.h @@ -697,6 +697,33 @@ enum flash_ex_op_types { * Reset flash device. */ FLASH_EX_OP_RESET = 0, + + /** + * Checks whether a block is marked as bad. As input it takes the address of the block + * (off_t *). As output it returns @ref flash_block_status (enum flash_block_status *). + */ + FLASH_EX_OP_IS_BAD_BLOCK = 1, + + /** + * Marks a block as bad. As input it takes the address of the block (off_t *). There is no + * output. + */ + FLASH_EX_OP_MARK_BAD_BLOCK = 2, +}; + +/** + * @brief Enumeration for flash block status. + */ +enum flash_block_status { + /** + * Block is functional. + */ + FLASH_BLOCK_GOOD = 0, + + /** + * Block is marked as bad. + */ + FLASH_BLOCK_BAD = 1, }; static inline int z_impl_flash_ex_op(const struct device *dev, uint16_t code, diff --git a/modules/Kconfig b/modules/Kconfig index 3ff629c980fad..46b2e82b8f247 100644 --- a/modules/Kconfig +++ b/modules/Kconfig @@ -127,6 +127,9 @@ comment "cmsis-dsp module not available." comment "cmsis-nn module not available." depends on !ZEPHYR_CMSIS_NN_MODULE +comment "Dhara module not available." + depends on !ZEPHYR_DHARA_MODULE + # This ensures that symbols are available in Kconfig for dependency checking # and referencing, while keeping the settings themselves unavailable when the # modules are not present in the workspace diff --git a/modules/dhara/CMakeLists.txt b/modules/dhara/CMakeLists.txt new file mode 100644 index 0000000000000..287804378f78b --- /dev/null +++ b/modules/dhara/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_DISK_DRIVER_FTL) + zephyr_include_directories(${ZEPHYR_CURRENT_MODULE_DIR}/include) + + file(GLOB dhara_sources ${ZEPHYR_CURRENT_MODULE_DIR}/dhara/*.c) + + zephyr_library() + zephyr_library_sources( + ${dhara_sources} + ) +endif() diff --git a/modules/dhara/Kconfig b/modules/dhara/Kconfig new file mode 100644 index 0000000000000..153a77763fb56 --- /dev/null +++ b/modules/dhara/Kconfig @@ -0,0 +1,5 @@ +# Copyright (c) 2025 Endress+Hauser GmbH+Co. KG +# SPDX-License-Identifier: Apache-2.0 + +config ZEPHYR_DHARA_MODULE + bool diff --git a/modules/fatfs/zephyr_fatfs_config.h b/modules/fatfs/zephyr_fatfs_config.h index e3d59c499addc..9896b953e11c9 100644 --- a/modules/fatfs/zephyr_fatfs_config.h +++ b/modules/fatfs/zephyr_fatfs_config.h @@ -109,12 +109,13 @@ #define _FF_DISK_NAME(node) DT_PROP(node, disk_name), #undef FF_VOLUME_STRS -#define FF_VOLUME_STRS \ - DT_FOREACH_STATUS_OKAY(zephyr_flash_disk, _FF_DISK_NAME) \ - DT_FOREACH_STATUS_OKAY(zephyr_ram_disk, _FF_DISK_NAME) \ - DT_FOREACH_STATUS_OKAY(zephyr_sdmmc_disk, _FF_DISK_NAME) \ - DT_FOREACH_STATUS_OKAY(zephyr_mmc_disk, _FF_DISK_NAME) \ - DT_FOREACH_STATUS_OKAY(st_stm32_sdmmc, _FF_DISK_NAME) +#define FF_VOLUME_STRS \ + DT_FOREACH_STATUS_OKAY(zephyr_flash_disk, _FF_DISK_NAME) \ + DT_FOREACH_STATUS_OKAY(zephyr_ram_disk, _FF_DISK_NAME) \ + DT_FOREACH_STATUS_OKAY(zephyr_sdmmc_disk, _FF_DISK_NAME) \ + DT_FOREACH_STATUS_OKAY(zephyr_mmc_disk, _FF_DISK_NAME) \ + DT_FOREACH_STATUS_OKAY(st_stm32_sdmmc, _FF_DISK_NAME) \ + DT_FOREACH_STATUS_OKAY(zephyr_ftl_dhara, _FF_DISK_NAME) #undef FF_VOLUMES #define FF_VOLUMES NUM_VA_ARGS_LESS_1(FF_VOLUME_STRS _) diff --git a/tests/drivers/disk/disk_access/boards/native_sim_ftl.overlay b/tests/drivers/disk/disk_access/boards/native_sim_ftl.overlay new file mode 100644 index 0000000000000..08a74f28a8400 --- /dev/null +++ b/tests/drivers/disk/disk_access/boards/native_sim_ftl.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Endress+Hauser GmbH+Co. KG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flashcontroller0 { + reg = <0x00000000 DT_SIZE_K(1024)>; +}; + +&flash0 { + reg = <0x00000000 DT_SIZE_K(1024)>; + erase-block-size = ; + write-block-size = <512>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + ftl_partition: partition@0 { + label = "ftl"; + reg = <0x00000000 DT_SIZE_K(1024)>; + }; + }; +}; + +/ { + ftl { + compatible = "zephyr,ftl-dhara"; + partition = <&ftl_partition>; + disk-name = "NAND"; + buffer-size = <512>; + }; +}; diff --git a/tests/drivers/disk/disk_access/src/main.c b/tests/drivers/disk/disk_access/src/main.c index 252dd3f6db7fc..e54d6dcb5daf6 100644 --- a/tests/drivers/disk/disk_access/src/main.c +++ b/tests/drivers/disk/disk_access/src/main.c @@ -24,7 +24,7 @@ #define DISK_NAME_PHYS "SD" #elif defined(CONFIG_DISK_DRIVER_MMC) #define DISK_NAME_PHYS "SD2" -#elif defined(CONFIG_DISK_DRIVER_FLASH) +#elif defined(CONFIG_DISK_DRIVER_FLASH) || defined(CONFIG_DISK_DRIVER_FTL) #define DISK_NAME_PHYS "NAND" #elif defined(CONFIG_NVME) #define DISK_NAME_PHYS "nvme0n0" diff --git a/tests/drivers/disk/disk_access/testcase.yaml b/tests/drivers/disk/disk_access/testcase.yaml index ad985f42c88c9..41b0a5ec143fa 100644 --- a/tests/drivers/disk/disk_access/testcase.yaml +++ b/tests/drivers/disk/disk_access/testcase.yaml @@ -25,6 +25,14 @@ tests: platform_allow: - native_sim/native/64 - native_sim + drivers.disk.ftl: + extra_args: DTC_OVERLAY_FILE=boards/native_sim_ftl.overlay + extra_configs: + - CONFIG_DISK_DRIVER_FTL=y + - CONFIG_FLASH_SIMULATOR_EX_OP=y + platform_allow: + - native_sim/native/64 + - native_sim drivers.disk.loopback: extra_configs: - CONFIG_DISK_DRIVER_LOOPBACK=y diff --git a/west.yml b/west.yml index 43f8299460e24..c0bb7facfc1a8 100644 --- a/west.yml +++ b/west.yml @@ -133,6 +133,9 @@ manifest: path: modules/hal/cmsis_6 groups: - hal + - name: dhara + revision: 6f163ca05e174b168b4d148160b50eeaeeb561fc + path: modules/lib/dhara - name: edtt revision: c282625e694f0b53ea53e13231ea6d2f49411768 path: tools/edtt