diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 4a02268a2b4507a..db687d55c3e3610 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -26,6 +26,7 @@ if(CONFIG_FLASH_SIMULATOR) zephyr_library_sources(flash_simulator_native.c) endif() endif() +zephyr_library_sources_ifdef(CONFIG_DISK_FLASH_DRIVER disk_flash.c) zephyr_library_sources_ifdef(CONFIG_SPI_FLASH_AT45 spi_flash_at45.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_ITE_IT8XXX2 flash_ite_it8xxx2.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NRF soc_flash_nrf.c) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index 698190e7780af23..bac1ac0b4dcc01f 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -138,6 +138,8 @@ source "drivers/flash/Kconfig.sam" source "drivers/flash/Kconfig.simulator" +source "drivers/flash/Kconfig.disk_flash" + source "drivers/flash/Kconfig.rv32m1" source "drivers/flash/Kconfig.nordic_qspi_nor" diff --git a/drivers/flash/Kconfig.disk_flash b/drivers/flash/Kconfig.disk_flash new file mode 100644 index 000000000000000..3fbaf71074cfca5 --- /dev/null +++ b/drivers/flash/Kconfig.disk_flash @@ -0,0 +1,14 @@ +# Disk flash emulator config + +# Copyright (c) 2023 Endress+Hauser AG +# SPDX-License-Identifier: Apache-2.0 + +config DISK_FLASH_DRIVER + bool "Use the flash on disk emulator" + default y + depends on DT_HAS_ZEPHYR_DISK_FLASH_ENABLED + select FLASH_HAS_DRIVER_ENABLED + help + This option enables the flash on disk emulator + which can be used to emulate a flash on a disk + device like sd-card or ramdisk. diff --git a/drivers/flash/disk_flash.c b/drivers/flash/disk_flash.c new file mode 100644 index 000000000000000..7ca6ef0d44d0526 --- /dev/null +++ b/drivers/flash/disk_flash.c @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2023 Endress+Hauser AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_disk_flash + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(disk_flash, CONFIG_FLASH_LOG_LEVEL); + +struct disk_flash_data { + struct k_sem sem; + uint8_t *const sector_buf; + uint16_t const sector_buf_size; + uint32_t disk_total_sector_cnt; + uint32_t disk_sector_size; +}; + +struct disk_flash_config { + uint32_t flash_size; + struct flash_parameters flash_parameters; + char *disk_name; + uint32_t disk_offset; + + IF_ENABLED(CONFIG_FLASH_PAGE_LAYOUT, (struct flash_pages_layout fpl;)) +}; + +static void acquire_device(const struct device *dev) +{ + struct disk_flash_data *const data = dev->data; + + k_sem_take(&data->sem, K_FOREVER); +} + +static void release_device(const struct device *dev) +{ + struct disk_flash_data *const data = dev->data; + + k_sem_give(&data->sem); +} + +static int check_boundary(const struct device *dev, off_t offset, size_t len) +{ + int ret = 0; + const struct disk_flash_config *cfg = dev->config; + size_t operation_end = offset + len; + + if (cfg->flash_size < operation_end || offset < 0) { + LOG_ERR("Operation out of bounds"); + ret = -EINVAL; + } + + return ret; +} + +static int disk_flash_read(const struct device *dev, off_t offset, void *dst, size_t len) +{ + int ret = 0; + uint8_t *dst_buf = dst; + const struct disk_flash_config *cfg = dev->config; + struct disk_flash_data *data = dev->data; + uint32_t write_cursor = 0; + + acquire_device(dev); + + ret = check_boundary(dev, offset, len); + if (ret != 0) { + goto release_device; + } + + if (data->disk_sector_size == 0) { + ret = -EINVAL; + goto release_device; + } + + while (write_cursor < len) { + uint32_t read_cursor = cfg->disk_offset + offset + write_cursor; + uint32_t read_sector = read_cursor / data->disk_sector_size; + uint32_t read_padding = read_cursor % data->disk_sector_size; + uint32_t len_write = (len - write_cursor); + + if (read_sector < 0 || read_sector >= data->disk_total_sector_cnt) { + ret = -EINVAL; + goto release_device; + } + + if (disk_access_read(cfg->disk_name, data->sector_buf, read_sector, 1)) { + ret = -EINVAL; + goto release_device; + } + + if (len_write > data->sector_buf_size - read_padding) { + len_write = data->sector_buf_size - read_padding; + } + + memcpy(&dst_buf[write_cursor], &data->sector_buf[read_padding], len_write); + write_cursor += len_write; + } + +release_device: + release_device(dev); + return ret; +} + +static int disk_flash_write(const struct device *dev, off_t offset, const void *src, size_t len) +{ + int ret = 0; + const uint8_t *src_buf = src; + const struct disk_flash_config *cfg = dev->config; + struct disk_flash_data *data = dev->data; + uint32_t read_cursor = 0; + + acquire_device(dev); + + ret = check_boundary(dev, offset, len); + if (ret != 0) { + goto release_device; + } + + if (data->disk_sector_size == 0) { + ret = -EINVAL; + goto release_device; + } + + while (read_cursor < len) { + uint32_t write_cursor = cfg->disk_offset + offset + read_cursor; + uint32_t write_sector = write_cursor / data->disk_sector_size; + uint32_t write_padding = write_cursor % data->disk_sector_size; + uint32_t len_write = (len - read_cursor); + + if (write_sector < 0 || write_sector >= data->disk_total_sector_cnt) { + LOG_ERR("Sector out of bounds"); + ret = -EINVAL; + goto release_device; + } + + if (disk_access_read(cfg->disk_name, data->sector_buf, write_sector, 1)) { + ret = -EINVAL; + goto release_device; + } + + if (len_write > data->sector_buf_size - write_padding) { + len_write = data->sector_buf_size - write_padding; + } + + memcpy(&data->sector_buf[write_padding], &src_buf[read_cursor], len_write); + + if (disk_access_write(cfg->disk_name, data->sector_buf, write_sector, 1)) { + ret = -EINVAL; + goto release_device; + } + read_cursor += len_write; + } + +release_device: + release_device(dev); + return ret; +} + +static int disk_flash_erase(const struct device *dev, off_t offset, size_t size) +{ + int ret = 0; + const struct disk_flash_config *cfg = dev->config; + struct disk_flash_data *data = dev->data; + uint32_t read_cursor = 0; + + acquire_device(dev); + + ret = check_boundary(dev, offset, size); + if (ret != 0) { + goto release_device; + } + + if (data->disk_sector_size == 0) { + ret = -EINVAL; + goto release_device; + } + + while (read_cursor < size) { + uint32_t write_cursor = cfg->disk_offset + offset + read_cursor; + uint32_t write_sector = write_cursor / data->disk_sector_size; + uint32_t write_padding = write_cursor % data->disk_sector_size; + uint32_t len_write = (size - read_cursor); + + if (write_sector < 0 || write_sector >= data->disk_total_sector_cnt) { + ret = -EINVAL; + goto release_device; + } + + if (disk_access_read(cfg->disk_name, data->sector_buf, write_sector, 1)) { + ret = -EINVAL; + goto release_device; + } + + if (len_write > data->sector_buf_size - write_padding) { + len_write = data->sector_buf_size - write_padding; + } + + memset(&data->sector_buf[write_padding], + cfg->flash_parameters.erase_value, + len_write); + + if (disk_access_write(cfg->disk_name, data->sector_buf, write_sector, 1)) { + ret = -EINVAL; + goto release_device; + } + read_cursor += len_write; + } + +release_device: + release_device(dev); + return ret; +} + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +static void disk_flash_pages_layout(const struct device *dev, + const struct flash_pages_layout **layout, size_t *layout_size) +{ + const struct disk_flash_config *cfg = dev->config; + + *layout = &cfg->fpl; + *layout_size = 1; +} +#endif + +static const struct flash_parameters *disk_flash_get_parameters(const struct device *dev) +{ + const struct disk_flash_config *cfg = dev->config; + + return &cfg->flash_parameters; +} + +static const struct flash_driver_api disk_flash_api = { + .read = disk_flash_read, + .write = disk_flash_write, + .erase = disk_flash_erase, + .get_parameters = disk_flash_get_parameters, +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + .page_layout = disk_flash_pages_layout, +#endif +#if defined(CONFIG_FLASH_JESD216_API) + .sfdp_read = NULL, + .read_jedec_id = NULL, +#endif +#if defined(CONFIG_FLASH_EX_OP_ENABLED) + .ex_op = NULL, +#endif +}; + +static int disk_flash_init(const struct device *dev) +{ + int res = 0; + struct disk_flash_data *data = dev->data; + const struct disk_flash_config *cfg = dev->config; + size_t disk_size = 0; + + k_sem_init(&data->sem, 1, 1); + + res = disk_access_init(cfg->disk_name); + if (res != 0) { + LOG_ERR("init disk failed: %d", res); + goto init_done; + } + + res = disk_access_ioctl(cfg->disk_name, DISK_IOCTL_GET_SECTOR_COUNT, + &data->disk_total_sector_cnt); + if (res != 0) { + LOG_ERR("read total sector cnt failed: %d", res); + goto init_done; + } + + + res = disk_access_ioctl(cfg->disk_name, DISK_IOCTL_GET_SECTOR_SIZE, + &data->disk_sector_size); + if (res != 0) { + LOG_ERR("read sector size failed: %d", res); + goto init_done; + } + + if (data->disk_sector_size > data->sector_buf_size) { + LOG_ERR("sector size %u of disk to big for buffer %u", data->disk_sector_size, + (uint32_t)data->sector_buf_size); + res = -EINVAL; + goto init_done; + } + + if (cfg->disk_offset % data->disk_sector_size != 0) { + LOG_WRN("offset is not aligned with disk sectors (disk sector size: %u)", + data->disk_sector_size); + } + + disk_size = data->disk_sector_size * data->disk_total_sector_cnt; + if ((disk_size - cfg->disk_offset) < cfg->flash_size) { + LOG_ERR("Underlying disk to small to support flash: %lu < %d", + (unsigned long)(disk_size - cfg->disk_offset), + cfg->flash_size); + res = -EINVAL; + goto init_done; + } + +init_done: + return res; +} + +#define DISK_FLASH_DEFINE(index) \ + BUILD_ASSERT(DT_INST_PROP(index, size) > 0, "flash_size needs to be > 0"); \ + BUILD_ASSERT(DT_INST_PROP(index, disk_offset) >= 0, "disk_offset needs to be >= 0"); \ + uint8_t sector_buf_##index[DT_INST_PROP(index, disk_sector_size)]; \ + static struct disk_flash_data disk_flash_data_##index = { \ + .sector_buf = sector_buf_##index, \ + .sector_buf_size = sizeof(sector_buf_##index), \ + }; \ + static const struct disk_flash_config disk_flash_config_##index = { \ + .flash_size = DT_INST_PROP(index, size), \ + .flash_parameters = \ + { \ + .write_block_size = 1, \ + .erase_value = 0xFF \ + }, \ + IF_ENABLED( \ + CONFIG_FLASH_PAGE_LAYOUT, \ + (.fpl = \ + { \ + .pages_count = DT_INST_PROP(index, size), \ + .pages_size = 1 \ + }, \ + ) \ + ) \ + .disk_name = DT_INST_PROP(index, disk_name), \ + .disk_offset = DT_INST_PROP(index, disk_offset), \ + }; \ + DEVICE_DT_INST_DEFINE(index, &disk_flash_init, NULL, &disk_flash_data_##index, \ + &disk_flash_config_##index, \ + DT_STRING_TOKEN(DT_DRV_INST(index), init_level), \ + CONFIG_FLASH_INIT_PRIORITY, &disk_flash_api); + +DT_INST_FOREACH_STATUS_OKAY(DISK_FLASH_DEFINE) diff --git a/dts/bindings/flash_controller/zephyr,disk-flash.yaml b/dts/bindings/flash_controller/zephyr,disk-flash.yaml new file mode 100644 index 000000000000000..91962eab5a5bc03 --- /dev/null +++ b/dts/bindings/flash_controller/zephyr,disk-flash.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: Apache-2.0 + +description: Emulates a flash using the disk access subsystem + +compatible: "zephyr,disk-flash" + +include: base.yaml + +properties: + size: + type: int + description: emulated flash capacity + required: true + disk-name: + type: string + description: the name of the underlying disk to be used + required: true + disk-sector-size: + type: int + description: the sector size in byte of the underlying disk to be used + required: true + disk-offset: + type: int + description: > + offset on the disk in bytes. If the offset is 10 the 10th byte will + be the first one used. + default: 0 + init-level: + type: string + description: > + The initialization level of this driver. It is important, that the underlying disk has + already been initialized. + default: "POST_KERNEL" diff --git a/samples/drivers/flash_shell/disk_flash.conf b/samples/drivers/flash_shell/disk_flash.conf new file mode 100644 index 000000000000000..67d922ae35e5007 --- /dev/null +++ b/samples/drivers/flash_shell/disk_flash.conf @@ -0,0 +1,7 @@ +# RAM-Disk config +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVERS=y +CONFIG_DISK_DRIVER_RAM=y + +# Initialize flash_disk after ramdisk +CONFIG_FLASH_INIT_PRIORITY=51 diff --git a/samples/drivers/flash_shell/disk_flash.overlay b/samples/drivers/flash_shell/disk_flash.overlay new file mode 100644 index 000000000000000..3cc654229344479 --- /dev/null +++ b/samples/drivers/flash_shell/disk_flash.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Endress+Hauser AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ramdisk0 { + compatible = "zephyr,ram-disk"; + disk-name = "DISK0"; + sector-size = <512>; + sector-count = <2>; + }; + + disk_flash: disk_flash { + compatible = "zephyr,disk-flash"; + status = "okay"; + size = <512>; + disk-name = "DISK0"; + disk-sector-size = <512>; + disk-offset = <512>; + }; +};