diff --git a/drivers/disk/CMakeLists.txt b/drivers/disk/CMakeLists.txt index 0b786e89c5b..0202238a1d2 100644 --- a/drivers/disk/CMakeLists.txt +++ b/drivers/disk/CMakeLists.txt @@ -7,6 +7,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_FLASH flashdisk.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_RAM ramdisk.c) +zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_LOOPBACK loopback_disk.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_STM32 sdmmc_stm32.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_SUBSYS sdmmc_subsys.c) diff --git a/drivers/disk/Kconfig b/drivers/disk/Kconfig index 1412de04f98..9c1723e6e8d 100644 --- a/drivers/disk/Kconfig +++ b/drivers/disk/Kconfig @@ -12,6 +12,7 @@ source "drivers/disk/Kconfig.ram" source "drivers/disk/Kconfig.flash" source "drivers/disk/Kconfig.sdmmc" source "drivers/disk/Kconfig.mmc" +source "drivers/disk/Kconfig.loopback" rsource "nvme/Kconfig" diff --git a/drivers/disk/Kconfig.loopback b/drivers/disk/Kconfig.loopback new file mode 100644 index 00000000000..9b429cc8739 --- /dev/null +++ b/drivers/disk/Kconfig.loopback @@ -0,0 +1,22 @@ +# Copyright (c) 2024 Embedded Solutions GmbH +# SPDX-License-Identifier: Apache-2.0 + +menuconfig DISK_DRIVER_LOOPBACK + bool "Loopback Disk" + depends on FILE_SYSTEM + help + Enables mounting the contents of a file as a separate disk. + +if DISK_DRIVER_LOOPBACK + +config LOOPBACK_DISK_SECTOR_SIZE + int "Loopback disk sector size" + default 512 + help + Sets the sector size used for loopback-mounted disks. + +module = LOOPBACK_DISK +module-str = loopback_disk +source "subsys/logging/Kconfig.template.log_config" + +endif # DISK_DRIVER_LOOPBACK diff --git a/drivers/disk/loopback_disk.c b/drivers/disk/loopback_disk.c new file mode 100644 index 00000000000..50b7beeaabc --- /dev/null +++ b/drivers/disk/loopback_disk.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2023-2024 Embedded Solutions GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(loopback_disk_access, CONFIG_LOOPBACK_DISK_LOG_LEVEL); + +#define LOOPBACK_SECTOR_SIZE CONFIG_LOOPBACK_DISK_SECTOR_SIZE + +static inline struct loopback_disk_access *get_ctx(struct disk_info *info) +{ + return CONTAINER_OF(info, struct loopback_disk_access, info); +} + +static int loopback_disk_access_init(struct disk_info *disk) +{ + return 0; +} +static int loopback_disk_access_status(struct disk_info *disk) +{ + return DISK_STATUS_OK; +} +static int loopback_disk_access_read(struct disk_info *disk, uint8_t *data_buf, + uint32_t start_sector, uint32_t num_sector) +{ + struct loopback_disk_access *ctx = get_ctx(disk); + + int ret = fs_seek(&ctx->file, start_sector * LOOPBACK_SECTOR_SIZE, FS_SEEK_SET); + + if (ret != 0) { + LOG_ERR("Failed to seek backing file: %d", ret); + return ret; + } + + const size_t total_len = num_sector * LOOPBACK_SECTOR_SIZE; + size_t len_left = total_len; + + while (len_left > 0) { + ret = fs_read(&ctx->file, data_buf, len_left); + if (ret < 0) { + LOG_ERR("Failed to read from backing file: %d", ret); + return ret; + } + if (ret == 0) { + LOG_WRN("Tried to read past end of backing file"); + return -EIO; + } + __ASSERT(ret <= len_left, + "fs_read returned more than we asked for: %d instead of %ld", ret, + len_left); + len_left -= ret; + } + + return 0; +} +static int loopback_disk_access_write(struct disk_info *disk, const uint8_t *data_buf, + uint32_t start_sector, uint32_t num_sector) +{ + struct loopback_disk_access *ctx = get_ctx(disk); + + if (start_sector + num_sector > ctx->num_sectors) { + LOG_WRN("Tried to write past end of backing file"); + return -EIO; + } + + int ret = fs_seek(&ctx->file, start_sector * LOOPBACK_SECTOR_SIZE, FS_SEEK_SET); + + if (ret != 0) { + LOG_ERR("Failed to seek backing file: %d", ret); + return ret; + } + + const size_t total_len = num_sector * LOOPBACK_SECTOR_SIZE; + size_t buf_offset = 0; + + while (buf_offset < total_len) { + ret = fs_write(&ctx->file, &data_buf[buf_offset], total_len - buf_offset); + if (ret < 0) { + LOG_ERR("Failed to write to backing file: %d", ret); + return ret; + } + if (ret == 0) { + LOG_ERR("0-byte write to backing file"); + return -EIO; + } + buf_offset += ret; + } + + return 0; +} +static int loopback_disk_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) +{ + struct loopback_disk_access *ctx = get_ctx(disk); + + switch (cmd) { + case DISK_IOCTL_GET_SECTOR_COUNT: { + *(uint32_t *)buff = ctx->num_sectors; + return 0; + } + case DISK_IOCTL_GET_SECTOR_SIZE: { + *(uint32_t *)buff = LOOPBACK_SECTOR_SIZE; + return 0; + } + case DISK_IOCTL_CTRL_SYNC: + return fs_sync(&ctx->file); + default: + return -ENOTSUP; + } +} + +static const struct disk_operations loopback_disk_operations = { + .init = loopback_disk_access_init, + .status = loopback_disk_access_status, + .read = loopback_disk_access_read, + .write = loopback_disk_access_write, + .ioctl = loopback_disk_access_ioctl, +}; + +int loopback_disk_access_register(struct loopback_disk_access *ctx, const char *file_path, + const char *disk_access_name) +{ + ctx->file_path = file_path; + + ctx->info.name = disk_access_name; + ctx->info.ops = &loopback_disk_operations; + + struct fs_dirent entry; + int ret = fs_stat(ctx->file_path, &entry); + + if (ret != 0) { + LOG_ERR("Failed to stat backing file: %d", ret); + return ret; + } + if (entry.size % LOOPBACK_SECTOR_SIZE != 0) { + LOG_WRN("Backing file is not a multiple of sector size (%d bytes), " + "rounding down: %ld bytes", + LOOPBACK_SECTOR_SIZE, entry.size); + } + ctx->num_sectors = entry.size / LOOPBACK_SECTOR_SIZE; + + fs_file_t_init(&ctx->file); + ret = fs_open(&ctx->file, ctx->file_path, FS_O_READ | FS_O_WRITE); + if (ret != 0) { + LOG_ERR("Failed to open backing file: %d", ret); + return ret; + } + + ret = disk_access_register(&ctx->info); + if (ret != 0) { + LOG_ERR("Failed to register disk access: %d", ret); + return ret; + } + + return 0; +} + +int loopback_disk_access_unregister(struct loopback_disk_access *ctx) +{ + int ret; + + ret = disk_access_unregister(&ctx->info); + if (ret != 0) { + LOG_ERR("Failed to unregister disk access: %d", ret); + return ret; + } + ctx->info.name = NULL; + ctx->info.ops = NULL; + + ret = fs_close(&ctx->file); + if (ret != 0) { + LOG_ERR("Failed to close backing file: %d", ret); + return ret; + } + + return 0; +} diff --git a/include/zephyr/drivers/loopback_disk.h b/include/zephyr/drivers/loopback_disk.h new file mode 100644 index 00000000000..56bc2393d94 --- /dev/null +++ b/include/zephyr/drivers/loopback_disk.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 Embedded Solutions GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_LOOPBACK_DISK_ACCESS_H_ +#define ZEPHYR_INCLUDE_DRIVERS_LOOPBACK_DISK_ACCESS_H_ + +#include +#include + +/** + * @brief Context object for an active loopback disk device + */ +struct loopback_disk_access { + struct disk_info info; + const char *file_path; + struct fs_file_t file; + size_t num_sectors; +}; + +/** + * @brief Register a loopback disk device + * + * Registers a new loopback disk deviced backed by a file at the specified path. + * + * @details + * @p All parameters (ctx, file_path and disk_access_name) must point to data that will remain valid + * until the disk access is unregistered. This is trivially true for file_path and disk_access_name + * if they are string literals, but care must be taken for ctx, as well as for file_path and + * disk_access_name if they are constructed dynamically. + * + * @param ctx Preallocated context structure + * @param file_path Path to backing file + * @param disk_access_name Name of the created disk access (for disk_access_*() functions) + * + * @retval 0 on success; + * @retval <0 negative errno code, depending on file system of the backing file. + */ +int loopback_disk_access_register(struct loopback_disk_access *ctx, const char *file_path, + const char *disk_access_name); + +/** + * @brief Unregister a previously registered loopback disk device + * + * Cleans up resources used by the disk access. + * + * @param ctx Context structure previously passed to a successful invocation of + * loopback_disk_access_register() + * + * @retval 0 on success; + * @retval <0 negative errno code, depending on file system of the backing file. + */ +int loopback_disk_access_unregister(struct loopback_disk_access *ctx); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_LOOPBACK_DISK_ACCESS_H_ */