From fa0bae8c6f5ca54f880663240b9aec71508b8e07 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Fri, 14 Jul 2023 23:29:18 +0200 Subject: [PATCH 1/3] drivers: disk: add fakefat disk driver Fakefat disk emulates a disk formatted with the FAT file system. The driver supports FAT16 and FAT32 emulation and multiple instances. A backend can register at least 15 emulated files on a disk instance. The file structure contain read and write callbacks that are called when the filesystem driver accesses the volume. A backend can have multiple files with read callbacks, but usually only one or zero with write callbacks. Fakefat disk can be used for testing or to map services such as firmware updates to a file on the emulated disk. Signed-off-by: Johann Fischer --- drivers/disk/CMakeLists.txt | 3 + drivers/disk/Kconfig | 1 + drivers/disk/Kconfig.ffat | 19 + drivers/disk/ffatdisk.c | 976 ++++++++++++++++++++++++ drivers/disk/ffatdisk.ld | 3 + dts/bindings/disk/zephyr,ffat-disk.yaml | 55 ++ include/zephyr/storage/ffatdisk.h | 64 ++ 7 files changed, 1121 insertions(+) create mode 100644 drivers/disk/Kconfig.ffat create mode 100644 drivers/disk/ffatdisk.c create mode 100644 drivers/disk/ffatdisk.ld create mode 100644 dts/bindings/disk/zephyr,ffat-disk.yaml create mode 100644 include/zephyr/storage/ffatdisk.h diff --git a/drivers/disk/CMakeLists.txt b/drivers/disk/CMakeLists.txt index 0202238a1d2a..89178b0a677d 100644 --- a/drivers/disk/CMakeLists.txt +++ b/drivers/disk/CMakeLists.txt @@ -5,6 +5,7 @@ if(CONFIG_DISK_DRIVERS) zephyr_library() +zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_FFAT ffatdisk.c) 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) @@ -15,4 +16,6 @@ zephyr_library_sources_ifdef(CONFIG_MMC_SUBSYS mmc_subsys.c) add_subdirectory_ifdef(CONFIG_NVME nvme) +zephyr_linker_sources_ifdef(CONFIG_DISK_DRIVER_FFAT DATA_SECTIONS ffatdisk.ld) + endif() diff --git a/drivers/disk/Kconfig b/drivers/disk/Kconfig index 9c1723e6e8d3..dd9491d74298 100644 --- a/drivers/disk/Kconfig +++ b/drivers/disk/Kconfig @@ -9,6 +9,7 @@ menuconfig DISK_DRIVERS if DISK_DRIVERS source "drivers/disk/Kconfig.ram" +source "drivers/disk/Kconfig.ffat" source "drivers/disk/Kconfig.flash" source "drivers/disk/Kconfig.sdmmc" source "drivers/disk/Kconfig.mmc" diff --git a/drivers/disk/Kconfig.ffat b/drivers/disk/Kconfig.ffat new file mode 100644 index 000000000000..7f9bff2791b9 --- /dev/null +++ b/drivers/disk/Kconfig.ffat @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config DISK_DRIVER_FFAT + bool "Fake FAT Disk" + default y if DT_HAS_ZEPHYR_FFAT_DISK_ENABLED + help + Fake FAT disk emulates a disk formatted with the FAT file system + and provides very limited functionality. It can be used for testing + or to map services such as firmware updates to a file on + the emulated disk. + +if DISK_DRIVER_FFAT + +module = FFATDISK +module-str = ffatdisk +source "subsys/logging/Kconfig.template.log_config" + +endif # DISK_DRIVER_FFAT diff --git a/drivers/disk/ffatdisk.c b/drivers/disk/ffatdisk.c new file mode 100644 index 000000000000..a05af1766b7d --- /dev/null +++ b/drivers/disk/ffatdisk.c @@ -0,0 +1,976 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(ffat, CONFIG_FFATDISK_LOG_LEVEL); + +#define FAT16_CLUSTERS_MIN 0x0FF5U +#define FAT16_CLUSTERS_MAX 0xFFF4U +#define FAT16_END_OF_CHAIN 0xFFFFU +#define FAT16_FIRST_ENTRY 0xFFF8U + +#define FAT32_CLUSTERS_MIN 0x0000FFF5UL +#define FAT32_CLUSTERS_MAX 0x0FFFFFF4UL +#define FAT32_END_OF_CHAIN 0x0FFFFFFFUL +#define FAT32_FIRST_ENTRY 0x0FFFFFF8UL + +#define FAT_BS_SECTOR 0UL +#define FAT_BS_BACKUP_SECTOR 6UL +#define FAT_BS_SIG_WORD 0xAA55U +#define FAT_BS_SIG_WORD_OFFSET 510U + +#define FAT_FSI_SECTOR 1UL +#define FAT_FSI_BACKUP_SECTOR 7UL +#define FAT_FSI_LEAD_SIG 0x41615252UL +#define FAT_FSI_STRUC_SIG 0x61417272UL +#define FAT_FSI_TRAIL_SIG 0xAA550000UL + +#define FAT_DIR_ATTR_READ_ONLY BIT(0) +#define FAT_DIR_ATTR_HIDDEN BIT(1) +#define FAT_DIR_ATTR_SYSTEM BIT(2) +#define FAT_DIR_ATTR_VOLUME_ID BIT(3) +#define FAT_DIR_ATTR_DIRECTORY BIT(4) +#define FAT_DIR_ATTR_ARCHIVE BIT(5) + +/* + * Number of sectors/clusters for the root directory, informative only, + * should not be changed. So the number of files/directories is limited + * by this, but should be enough for all use cases. + */ +#define FFAT16_RD_SECTORS 1U +#define FFAT32_RD_CLUSTERS 1U + +/* + * For the FAT structures from the spec, we use the same identifier scheme, + * just dropped the prefix and converted from silly camel case. + */ + +struct fat16_ebpb { + uint8_t drv_num; /* Drive number */ + uint8_t reserved1; /* Reserved */ + uint8_t boot_sig; /* Extended boot signature */ + uint32_t vol_id; /* Volume serial number */ + uint8_t vol_lab[11]; /* Volume label */ + uint8_t fil_sys_type[8]; /* Filesystem type */ +} __packed; + +struct fat32_ebpb { + uint32_t fat_sz32; /* Number of sectors occupied by one FAT */ + uint16_t ext_flags; /* Flags */ + uint16_t fs_ver; /* Always 0 */ + uint32_t root_clus; /* First cluster of the root directory */ + uint16_t fs_info; /* Sector number of fsinfo structure */ + uint16_t bk_boot_sec; /* Sector number of fsinfo structure */ + uint8_t reserved[12]; /* Reserved */ + uint8_t drv_num; /* Drive number */ + uint8_t reserved1; /* Reserved */ + uint8_t boot_sig; /* Extended boot signature */ + uint32_t vol_id; /* Volume serial number */ + uint8_t vol_lab[11]; /* Volume label */ + uint8_t fil_sys_type[8]; /* File-system type */ +} __packed; + +struct fat_boot_sector { + uint8_t jump_boot[3]; /* Jump instruction */ + uint8_t oem_name[8]; /* OEM name or ID */ + /* BIOS parameter block */ + uint16_t byts_per_sec; /* Sector size in bytes + * 512, 1024, 2048 or 4096 + */ + uint8_t sec_per_clus; /* Number of sectors per cluster, + * 1, 2, 4, 8, 16, 32, 64 + */ + uint16_t rsvd_sec_cnt; /* Number of reserved sectors */ + uint8_t num_fats; /* Number of FATs */ + uint16_t root_ent_cnt; /* Number of root directory entries */ + uint16_t tot_sec16; /* Number of sectors */ + uint8_t media; /* Media code */ + uint16_t fat_sz16; /* FAT length in sectors */ + uint16_t sec_per_trk; /* Number of sectors per track */ + uint16_t num_heads; /* Number of heads */ + uint32_t hidd_sec; /* Hidden sectors */ + uint32_t tot_sec32; /* Total number of sectors */ + /* Extended BIOS parameter block */ + union { + struct fat16_ebpb ebpb16; + struct fat32_ebpb ebpb32; + }; +} __packed; + +struct fat_fsi_sector { + uint32_t lead_sig; + uint8_t reserved1[480]; + uint32_t struc_sig; + uint32_t free_count; + uint32_t nxt_free; + uint8_t reserved2[12]; + uint32_t trail_sig; +} __packed; + +BUILD_ASSERT(sizeof(struct fat_fsi_sector) == 512); + +struct fat_dir_entry { + char name[FAT_FILE_NAME_LEN]; /* File name */ + uint8_t attr; /* File attribute */ + uint8_t ntres; /* Reserved */ + uint8_t crt_time_tenth; /* Creation time 10ms */ + uint16_t crt_time; /* Creation time */ + uint16_t crt_date; /* Creation date */ + uint16_t lst_acc_date; /* Last access date */ + uint16_t fst_clus_hi; /* Zero on FAT16 */ + uint16_t wrt_time; /* Last modification time */ + uint16_t wrt_date; /* Last modification date */ + uint16_t fst_clus_lo; /* File name */ + uint32_t file_size; /* File size in bytes */ +} __packed; + +BUILD_ASSERT(sizeof(struct fat_dir_entry) == 32); + +struct ffatdisk_data { + sys_slist_t f_list; + struct disk_info info; + struct ffat_file vol_id; + uint32_t clusters_free; +}; + +struct ffatdisk_config { + const struct fat_boot_sector *fat_bs; + uint32_t fat_entries; + uint32_t root_ent_cnt; + uint32_t fdc; + uint32_t fat1_start; + uint32_t fat2_start; + uint32_t rd_start; + uint32_t data_start; + uint32_t sector_count; + uint32_t clusters; + uint32_t cluster_size; + void (*ffat_read)(const struct device *dev, uint8_t *const buf, + const uint32_t fat_sector); + bool fat32; +}; + +static ALWAYS_INLINE size_t bs_get_byts_per_sec(const struct device *dev) +{ + const struct ffatdisk_config *config = dev->config; + + return config->fat_bs->byts_per_sec; +} + +static ALWAYS_INLINE size_t bs_get_sec_per_clus(const struct device *dev) +{ + const struct ffatdisk_config *config = dev->config; + + return config->fat_bs->sec_per_clus; +} + +static ALWAYS_INLINE size_t file_size_in_clusters(const struct device *dev, + const struct ffat_file *const f, + uint32_t c_free) +{ + const struct ffatdisk_config *config = dev->config; + size_t q = f->size / config->cluster_size; + + if (f->size % config->cluster_size) { + q += 1; + } + + return MIN(q, c_free); +} + +static int ffat_init_files(struct disk_info *const disk) +{ + const struct device *dev = disk->dev; + const struct ffatdisk_config *config = dev->config; + struct ffatdisk_data *const data = dev->data; + struct ffat_file *const vol_id = &data->vol_id; + unsigned int rd_entries = 0; + uint32_t c_free = config->clusters; + uint32_t c_num = config->fdc; + + /* In the case of FAT32, one cluster is reserved for the root directory. */ + if (config->fat32) { + c_free -= FFAT32_RD_CLUSTERS; + } + + /* Add Volume ID entry */ + sys_slist_append(&data->f_list, &vol_id->node); + rd_entries++; + + LOG_INF("%u cluster free, cluster size %u", c_free, config->cluster_size); + + STRUCT_SECTION_FOREACH(ffat_file, f) { + uint32_t f_clusters; + + if (strcmp(disk->name, f->disk_name) != 0) { + /* Not our file */ + continue; + } + + f_clusters = file_size_in_clusters(dev, f, c_free); + + if (f_clusters) { + f->start = c_num; + f->end = c_num + f_clusters - 1; + c_num += f_clusters; + } + + LOG_INF("Add file to disk %s, start %zu, end %zu, size %zu (%zu)", + f->disk_name, f->start, f->end, f->size, f_clusters); + + /* Fix file name if necessary */ + for (size_t n = 0; n < sizeof(f->name); n++) { + f->name[n] = f->name[n] ? f->name[n] : ' '; + } + + if (f->wr_cb == NULL) { + f->attr |= FAT_DIR_ATTR_READ_ONLY; + } + + if (f->rd_cb == NULL) { + f->attr |= FAT_DIR_ATTR_HIDDEN; + } + + sys_slist_append(&data->f_list, &f->node); + c_free -= f_clusters; + + if (++rd_entries >= config->root_ent_cnt || !c_free) { + LOG_INF("Disk is full"); + break; + } + } + + data->clusters_free = c_free; + + return 0; +} + +static struct ffat_file *ffat_get_file(const struct device *dev, + const uint32_t cluster) +{ + struct ffatdisk_data *const data = dev->data; + struct ffat_file *f; + + SYS_SLIST_FOR_EACH_CONTAINER(&data->f_list, f, node) { + if (IN_RANGE(cluster, f->start, f->end)) { + return f; + } + } + + return NULL; +} + +static void ffat_read_bs(const struct device *dev, uint8_t *const buf) +{ + const struct ffatdisk_config *config = dev->config; + + memcpy(buf, config->fat_bs, sizeof(struct fat_boot_sector)); + + sys_put_le16(FAT_BS_SIG_WORD, &buf[FAT_BS_SIG_WORD_OFFSET]); +} + +static void ffat_read_fsi(const struct device *dev, uint8_t *const buf) +{ + struct ffatdisk_data *const data = dev->data; + struct fat_fsi_sector *fsi = (void *)buf; + + sys_put_le16(FAT_BS_SIG_WORD, &buf[FAT_BS_SIG_WORD_OFFSET]); + fsi->lead_sig = sys_cpu_to_le32(FAT_FSI_LEAD_SIG); + fsi->struc_sig = sys_cpu_to_le32(FAT_FSI_STRUC_SIG); + fsi->free_count = data->clusters_free; + fsi->nxt_free = 0xFFFFFFFFUL; + fsi->trail_sig = sys_cpu_to_le32(FAT_FSI_TRAIL_SIG); +} + +/* Read FAT entry, that is either pointer to the next entry or EOC. */ +static ALWAYS_INLINE void ffat_read_fat32_entry(struct ffat_file *const f, + uint32_t *const ptr, + const uint32_t e) +{ + if (e < f->end) { + *ptr = sys_cpu_to_le32(e + 1U); + } else { + *ptr = sys_cpu_to_le32(FAT32_END_OF_CHAIN); + } +} + +static inline void ffat_read_fat32(const struct device *dev, uint8_t *const buf, + const uint32_t fat_sector) +{ + const struct ffatdisk_config *config = dev->config; + struct ffat_file *f = NULL; + uint32_t *ptr = (void *)buf; + uint32_t fa_idx; + + fa_idx = fat_sector * config->fat_entries; + + for (uint32_t i = 0; i < config->fat_entries; i++) { + + /* Do not fetch file if it is in range. */ + if (f != NULL && IN_RANGE(fa_idx + i, f->start, f->end)) { + ffat_read_fat32_entry(f, &ptr[i], fa_idx + i); + continue; + } else { + /* Get file that is in range. */ + f = ffat_get_file(dev, fa_idx + i); + + if (f != NULL) { + ffat_read_fat32_entry(f, &ptr[i], fa_idx + i); + continue; + } + } + + if (fa_idx + i == 0U) { + ptr[i] = sys_cpu_to_le32(FAT32_FIRST_ENTRY); + continue; + } + + if (fa_idx + i == 1U) { + ptr[i] = sys_cpu_to_le32(FAT32_END_OF_CHAIN); + continue; + } + + if (fa_idx + i == 2U) { + ptr[i] = sys_cpu_to_le32(FAT32_END_OF_CHAIN); + continue; + } + } + + LOG_DBG("Read FAT sector %u", fat_sector); +} + +/* Read FAT entry, that is either pointer to the next entry or EOC. */ +static ALWAYS_INLINE void ffat_read_fat16_entry(struct ffat_file *const f, + uint16_t *const ptr, + const uint16_t e) +{ + if (e < f->end) { + *ptr = sys_cpu_to_le16(e + 1); + } else { + *ptr = sys_cpu_to_le16(FAT16_END_OF_CHAIN); + } +} + +static inline void ffat_read_fat16(const struct device *dev, uint8_t *const buf, + const uint32_t fat_sector) +{ + const struct ffatdisk_config *config = dev->config; + struct ffat_file *f = NULL; + uint16_t *ptr = (void *)buf; + uint32_t fa_idx; + + fa_idx = fat_sector * config->fat_entries; + + for (uint32_t i = 0; i < config->fat_entries; i++) { + + /* Do not fetch file if it is in range. */ + if (f != NULL && IN_RANGE(fa_idx + i, f->start, f->end)) { + ffat_read_fat16_entry(f, &ptr[i], fa_idx + i); + continue; + } else { + /* Get file that is in range. */ + f = ffat_get_file(dev, fa_idx + i); + + if (f != NULL) { + ffat_read_fat16_entry(f, &ptr[i], fa_idx + i); + continue; + } + } + + if (fa_idx + i == 0U) { + ptr[i] = sys_cpu_to_le16(FAT16_FIRST_ENTRY); + continue; + } + + if (fa_idx + i == 1U) { + ptr[i] = sys_cpu_to_le16(FAT16_END_OF_CHAIN); + continue; + } + } + + LOG_DBG("Read FAT sector %u", fat_sector); +} + +static void ffat_read_rd(const struct device *dev, uint8_t *const buf, + const uint32_t sector) +{ + const struct ffatdisk_config *config = dev->config; + struct ffatdisk_data *const data = dev->data; + struct fat_dir_entry *de = (void *)buf; + uint32_t rd_sector = sector - config->rd_start; + struct ffat_file *f; + + LOG_DBG("Read %u RD entries, sector %u (%u)", + config->root_ent_cnt, rd_sector, sector); + + if (rd_sector != 0) { + /* Ignore the higher sectors of a FAT32 cluster */ + return; + } + + SYS_SLIST_FOR_EACH_CONTAINER(&data->f_list, f, node) { + memcpy(de->name, f->name, sizeof(de->name)); + /* TODO Set wrt_time and wrt_data */ + de->wrt_time = 0; + de->wrt_date = 0; + + de->fst_clus_lo = f->start; + de->fst_clus_hi = config->fat32 ? f->start >> 16 : 0; + + de->file_size = f->size; + de->attr = f->attr; + + de++; + } +} + +/* Get any file with a valid write callback */ +static struct ffat_file *ffat_get_file_any_wr_cb(const struct device *dev) +{ + struct ffatdisk_data *const data = dev->data; + struct ffat_file *f; + + SYS_SLIST_FOR_EACH_CONTAINER(&data->f_list, f, node) { + if (f->wr_cb != NULL) { + return f; + } + } + + return NULL; +} + +/* Get file and file block index based on sector */ +static struct ffat_file *ffat_get_file_and_block(const struct device *dev, + const uint32_t sector, + uint32_t *const f_block) +{ + const struct ffatdisk_config *config = dev->config; + uint32_t d_sector = sector - config->data_start; + struct ffat_file *f; + uint32_t start_sector; + uint32_t c_num; + + /* Get cluster number from relative data region sector number */ + c_num = d_sector / bs_get_sec_per_clus(dev) + config->fdc; + + f = ffat_get_file(dev, c_num); + if (f == NULL) { + *f_block = 0UL; + return NULL; + } + + /* Get relative file start sector based on first cluster + * that is start of data region. + */ + start_sector = (f->start - config->fdc) * bs_get_sec_per_clus(dev); + + /* For the file write/read callback we need a block index + * relative to the file start sector. + */ + *f_block = d_sector - start_sector; + LOG_DBG("File block %u (s %u, d_s %u, c %u, start_s %u))", + *f_block, sector, d_sector, c_num, start_sector); + + return f; +} + +static void ffat_read_file(const struct device *dev, uint8_t *const buf, + const uint32_t sector) +{ + uint32_t byts_per_sec = bs_get_byts_per_sec(dev); + struct ffat_file *f; + uint32_t f_block; + + f = ffat_get_file_and_block(dev, sector, &f_block); + + if (f != NULL && f->rd_cb != NULL) { + f->rd_cb(f, f_block, buf, byts_per_sec); + LOG_DBG("Read file block %u (%u)", f_block, sector); + } +} + +static void ffat_read_sector(struct disk_info *const disk, uint8_t *const buf, + const uint32_t sector) +{ + const struct device *dev = disk->dev; + const struct ffatdisk_config *config = dev->config; + uint32_t fat_sector; + + if (IN_RANGE(sector, 0, config->sector_count - 1)) { + memset(buf, 0, bs_get_byts_per_sec(dev)); + } + + if (sector == FAT_BS_SECTOR || + (config->fat32 && sector == FAT_BS_BACKUP_SECTOR)) { + ffat_read_bs(dev, buf); + LOG_DBG("Read boot sector (%u)", sector); + + return; + } + + if (config->fat32 && + (sector == FAT_FSI_SECTOR || sector == FAT_FSI_BACKUP_SECTOR)) { + ffat_read_fsi(dev, buf); + LOG_DBG("Read FSI (%u)", sector); + + return; + } + + if (IN_RANGE(sector, config->fat1_start, config->fat2_start - 1)) { + fat_sector = sector - config->fat1_start; + LOG_DBG("Read FAT1 sector %u", fat_sector); + config->ffat_read(dev, buf, fat_sector); + + return; + } + + if (IN_RANGE(sector, config->fat2_start, config->rd_start - 1)) { + fat_sector = sector - config->fat2_start; + LOG_DBG("Read FAT2 sector %u", fat_sector); + config->ffat_read(dev, buf, fat_sector); + + return; + } + + if (IN_RANGE(sector, config->rd_start, config->data_start - 1)) { + ffat_read_rd(dev, buf, sector); + + return; + } + + if (IN_RANGE(sector, config->data_start, config->sector_count - 1)) { + ffat_read_file(dev, buf, sector); + + return; + } +} + +static int ffatdisk_access_read(struct disk_info *const disk, uint8_t *const buf, + const uint32_t sector, const uint32_t count) +{ + const struct device *dev = disk->dev; + const struct ffatdisk_config *config = dev->config; + uint32_t sector_max = sector + count; + + if (sector_max < sector || sector_max > config->sector_count) { + LOG_ERR("Sector %u is outside the range %u", + sector_max, config->sector_count); + return -EIO; + } + + for (uint32_t i = sector; i < sector_max; i++) { + ffat_read_sector(disk, buf, i); + } + + return 0; +} + +static int ffat_write_file(const struct device *dev, + const uint8_t *const buf, const uint32_t sector) +{ + struct ffatdisk_data *const data = dev->data; + uint32_t byts_per_sec = bs_get_byts_per_sec(dev); + struct ffat_file *f; + uint32_t f_block = 0UL; + + if (data->clusters_free) { + /* + * If there are free clusters on the volume, + * the filesystem driver can write to any of the free + * clusters and we cannot determine the sector number + * of the file. Therefore, there should be only one file + * with write callback. To get the exact sector (or block) + * number and other metadata, backends need to encapsulate + * the payload, which we do not care about at all. + */ + f = ffat_get_file_any_wr_cb(dev); + } else { + /* + * If there are no free clusters on the volume, + * we can determine the sector index of the file. + * This is nice, but less practical because some OS + * do not want to overwrite the file if there is no space. + */ + f = ffat_get_file_and_block(dev, sector, &f_block); + } + + if (f != NULL && f->wr_cb != NULL) { + f->wr_cb(f, f_block, buf, byts_per_sec); + LOG_DBG("Write file block %u (%u)", f_block, sector); + } + + return 0; +} + +static int ffat_write_sector(struct disk_info *const disk, + const uint8_t *const buf, const uint32_t sector) +{ + const struct device *dev = disk->dev; + const struct ffatdisk_config *config = dev->config; + uint32_t fat_sector; + + /* + * For now, we ignore write accesses from the (host) filesystem driver + * to all sectors except the data area. Perhaps we can use some of these + * to implement a mounted/unmounted state indication callback. + */ + + if (sector == FAT_BS_SECTOR || + (config->fat32 && sector == FAT_BS_BACKUP_SECTOR)) { + LOG_DBG("Write boot sector"); + + return 0; + } + + if (config->fat32 && + (sector == FAT_FSI_SECTOR || sector == FAT_FSI_BACKUP_SECTOR)) { + LOG_DBG("Write FSI %u", sector); + + return 0; + } + + if (IN_RANGE(sector, config->fat1_start, config->fat2_start - 1)) { + fat_sector = sector - config->fat1_start; + LOG_DBG("Write FAT1, sector %u (%u)", fat_sector, sector); + + return 0; + } + + if (IN_RANGE(sector, config->fat2_start, config->rd_start - 1)) { + fat_sector = sector - config->fat2_start; + LOG_DBG("Write FAT2, sector %u (%u)", fat_sector, sector); + + return 0; + } + + if (IN_RANGE(sector, config->rd_start, config->data_start - 1)) { + LOG_DBG("Write root directory (%u)", sector); + + return 0; + } + + if (IN_RANGE(sector, config->data_start, config->sector_count - 1)) { + return ffat_write_file(dev, buf, sector); + } + + return 0; +} + +static int ffatdisk_access_write(struct disk_info *disk, const uint8_t *buf, + uint32_t sector, uint32_t count) +{ + const struct device *dev = disk->dev; + const struct ffatdisk_config *config = dev->config; + uint32_t sector_max = sector + count; + + if (sector_max < sector || sector_max > config->sector_count) { + LOG_ERR("Sector %zu is outside the range %zu", + sector_max, config->sector_count); + return -EIO; + } + + for (uint32_t i = sector; i < sector_max; i++) { + ffat_write_sector(disk, buf, sector); + } + + return 0; +} + +static int ffatdisk_init(struct disk_info *disk) +{ + const struct device *dev = disk->dev; + const struct ffatdisk_config *config = dev->config; + + LOG_INF("FAT1 start %u, FAT2 start %u, RD start %u, data start %u", + config->fat1_start, config->fat2_start, + config->rd_start, config->data_start); + + LOG_INF("tot_sec16 %u, tot_sec32 %u fat_sz16 %u clusters %u", + config->fat_bs->tot_sec16, + config->fat_bs->tot_sec32, + config->fat_bs->fat_sz16, + config->clusters); + + return 0; +} + +static int ffatdisk_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buf) +{ + const struct ffatdisk_config *config = disk->dev->config; + + switch (cmd) { + case DISK_IOCTL_CTRL_SYNC: + break; + case DISK_IOCTL_GET_SECTOR_COUNT: + *(uint32_t *)buf = config->sector_count; + break; + case DISK_IOCTL_GET_SECTOR_SIZE: + *(uint32_t *)buf = bs_get_byts_per_sec(disk->dev); + break; + case DISK_IOCTL_GET_ERASE_BLOCK_SZ: + *(uint32_t *)buf = 1U; + break; + case DISK_IOCTL_CTRL_INIT: + return ffatdisk_init(disk); + case DISK_IOCTL_CTRL_DEINIT: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ffatdisk_access_status(struct disk_info *disk) +{ + return DISK_STATUS_OK; +} + +static int ffatdisk_preinit(const struct device *dev) +{ + struct ffatdisk_data *const data = dev->data; + + data->info.dev = dev; + ffat_init_files(&data->info); + + return disk_access_register(&data->info); +} + +static const struct disk_operations ffatdisk_ops = { + .init = ffatdisk_init, + .status = ffatdisk_access_status, + .read = ffatdisk_access_read, + .write = ffatdisk_access_write, + .ioctl = ffatdisk_access_ioctl, +}; + +#define DT_DRV_COMPAT zephyr_ffat_disk + +#define FFAT_DEFAULT_NUM_FAT 2U +#define FFAT_DEFAULT_MEDIA 0xF8U +#define FFAT_DEFAULT_DRV_NUM 0x80U +#define FFAT_EXTENDED_BOOT_SIG 0x29U + +#define FFAT_BS_TOT_SEC16(n) \ + (DT_INST_PROP(n, sector_count) > UINT16_MAX ? 0 : (uint16_t)DT_INST_PROP(n, sector_count)) + +#define FFAT_BS_TOT_SEC32(n) \ + (DT_INST_PROP(n, sector_count) > UINT16_MAX ? DT_INST_PROP(n, sector_count) : 0) + +/* Get the number of clusters on the volume rounded up */ +#define FFAT_CLUSTER_ROUND_UP(n) \ + DIV_ROUND_UP(DT_INST_PROP(n, sector_count), \ + DT_INST_PROP(n, sector_per_cluster)) + +/* Cluster size in bytes */ +#define FFAT_CLUSTER_SIZE(n) \ + (DT_INST_PROP(n, sector_size) * DT_INST_PROP(n, sector_per_cluster)) + +/* + * Get the number of FAT directory entries in a sector. + * We limit the number of files to what will fit into a sector. + * This means that the maximum for a 512 byte sector is 16 entries, + * which should be enough for the use cases of this design. + * We use the same macro for FAT32 to avoid any special handling. + */ +#define FFAT16_ROOT_ENT_CNT(n) \ + (DT_INST_PROP(n, sector_size) / sizeof(struct fat_dir_entry)) + +/* Get the number of FAT16 entries in a single sector */ +#define FFAT16_ENT_IN_SECTOR(n) \ + (DT_INST_PROP(n, sector_size) / sizeof(uint16_t)) + +/* Get the size of a FAT16 in sectors */ +#define FFAT_SZ16(n) \ + DIV_ROUND_UP(FFAT_CLUSTER_ROUND_UP(n), FFAT16_ENT_IN_SECTOR(n)) + +#define FFAT16_RSVD_SEC_CNT 1U + +/* Entries FAT[0] and FAT[1] are reserved */ +#define FAT16_FIRST_DATA_CLUSTER 2UL + +#define FFAT16_FAT1_START FFAT16_RSVD_SEC_CNT +#define FFAT16_FAT2_START(n) (FFAT16_RSVD_SEC_CNT + FFAT_SZ16(n)) +#define FFAT16_RD_START(n) (FFAT16_FAT2_START(n) + FFAT_SZ16(n)) +#define FFAT16_DATA_START(n) (FFAT16_RD_START(n) + FFAT16_RD_SECTORS) + +/* Number of sectors in the data region */ +#define FFAT16_DATA_SECTORS(n) \ + (DT_INST_PROP(n, sector_count) - FFAT16_DATA_START(n)) + +/* Actually number of clusters */ +#define FFAT16_CLUSTERS(n) \ + (FFAT16_DATA_SECTORS(n) / DT_INST_PROP(n, sector_per_cluster)) + +#define FFATDISK_CONFIG_FAT16_DEFINE(n) \ + BUILD_ASSERT(FFAT16_CLUSTERS(n) >= FAT16_CLUSTERS_MIN, \ + "FAT16 cluster count too low"); \ + BUILD_ASSERT(FFAT16_CLUSTERS(n) <= FAT16_CLUSTERS_MAX, \ + "FAT16 cluster count too high"); \ + \ + static const struct fat_boot_sector fat_bs_##n = { \ + .jump_boot = {0xEB, 0xFF, 0x90}, \ + .oem_name = "Zephyr ", \ + .byts_per_sec = DT_INST_PROP(n, sector_size), \ + .sec_per_clus = DT_INST_PROP(n, sector_per_cluster), \ + .rsvd_sec_cnt = FFAT16_RSVD_SEC_CNT, \ + .num_fats = FFAT_DEFAULT_NUM_FAT, \ + .root_ent_cnt = FFAT16_ROOT_ENT_CNT(n), \ + .tot_sec16 = FFAT_BS_TOT_SEC16(n), \ + .media = FFAT_DEFAULT_MEDIA, \ + .fat_sz16 = FFAT_SZ16(n), \ + .sec_per_trk = 1U, \ + .num_heads = 1U, \ + .hidd_sec = 0U, \ + .tot_sec32 = FFAT_BS_TOT_SEC32(n), \ + .ebpb16 = { \ + .drv_num = FFAT_DEFAULT_DRV_NUM, \ + .reserved1 = 0U, \ + .boot_sig = FFAT_EXTENDED_BOOT_SIG, \ + .vol_id = 0x00420042UL, \ + .vol_lab = "NO NAME ", \ + .fil_sys_type = "FAT16 ", \ + }, \ + }; \ + \ + static const struct ffatdisk_config ffatdisk_config_##n = { \ + .fat_bs = &fat_bs_##n, \ + .sector_count = DT_INST_PROP(n, sector_count), \ + .fat_entries = FFAT16_ENT_IN_SECTOR(n), \ + .root_ent_cnt = FFAT16_ROOT_ENT_CNT(n), \ + .fdc = FAT16_FIRST_DATA_CLUSTER, \ + .fat1_start = FFAT16_FAT1_START, \ + .fat2_start = FFAT16_FAT2_START(n), \ + .rd_start = FFAT16_RD_START(n), \ + .data_start = FFAT16_DATA_START(n), \ + .clusters = FFAT16_CLUSTERS(n), \ + .cluster_size = FFAT_CLUSTER_SIZE(n), \ + .ffat_read = ffat_read_fat16, \ + .fat32 = false, \ + } + +/* Get the number of FAT32 entries in a single sector */ +#define FFAT32_ENT_IN_SECTOR(n) (DT_INST_PROP(n, sector_size) / sizeof(uint32_t)) + +/* Get the size of a FAT32 in sectors */ +#define FFAT_SZ32(n) \ + DIV_ROUND_UP(FFAT_CLUSTER_ROUND_UP(n), FFAT32_ENT_IN_SECTOR(n)) + +#define FFAT32_RSVD_SEC_CNT 16U + +/* Entries FAT[0] and FAT[1] are reserved, FAT[3] is root directory */ +#define FAT32_FIRST_DATA_CLUSTER 3UL + +/* number of clusters */ +#define FFAT32_RD_SECTORS(n) \ + (FFAT32_RD_CLUSTERS * DT_INST_PROP(n, sector_per_cluster)) + +#define FFAT32_FAT1_START FFAT32_RSVD_SEC_CNT +#define FFAT32_FAT2_START(n) (FFAT32_RSVD_SEC_CNT + FFAT_SZ32(n)) +#define FFAT32_RD_START(n) (FFAT32_FAT2_START(n) + FFAT_SZ32(n)) +#define FFAT32_DATA_START(n) (FFAT32_RD_START(n) + FFAT32_RD_SECTORS(n)) + +/* Number of sectors in the data region */ +#define FFAT32_DATA_SECTORS(n) \ + (DT_INST_PROP(n, sector_count) - FFAT32_RD_START(n)) + +/* Actually number of clusters */ +#define FFAT32_CLUSTERS(n) \ + (FFAT32_DATA_SECTORS(n) / DT_INST_PROP(n, sector_per_cluster)) + +#define FFATDISK_CONFIG_FAT32_DEFINE(n) \ + BUILD_ASSERT(FFAT32_CLUSTERS(n) >= FAT32_CLUSTERS_MIN, \ + "FAT32 cluster count too low"); \ + BUILD_ASSERT(FFAT32_CLUSTERS(n) <= FAT32_CLUSTERS_MAX, \ + "FAT32 cluster count too high"); \ + \ + static const struct fat_boot_sector fat_bs_##n = { \ + .jump_boot = {0xEB, 0xFF, 0x90}, \ + .oem_name = "Zephyr ", \ + .byts_per_sec = DT_INST_PROP(n, sector_size), \ + .sec_per_clus = DT_INST_PROP(n, sector_per_cluster), \ + .rsvd_sec_cnt = FFAT32_RSVD_SEC_CNT, \ + .num_fats = FFAT_DEFAULT_NUM_FAT, \ + .root_ent_cnt = 0U, \ + .tot_sec16 = 0U, \ + .media = FFAT_DEFAULT_MEDIA, \ + .fat_sz16 = 0U, \ + .sec_per_trk = 1U, \ + .num_heads = 1U, \ + .hidd_sec = 0U, \ + .tot_sec32 = FFAT_BS_TOT_SEC32(n), \ + .ebpb32 = { \ + .fat_sz32 = FFAT_SZ32(n), \ + .ext_flags = 0U, \ + .fs_ver = 0U, \ + .root_clus = FAT32_FIRST_DATA_CLUSTER - 1UL, \ + .fs_info = 1U, \ + .bk_boot_sec = 6U, \ + .drv_num = FFAT_DEFAULT_DRV_NUM, \ + .reserved1 = 0U, \ + .boot_sig = FFAT_EXTENDED_BOOT_SIG, \ + .vol_id = 0x00420042UL, \ + .vol_lab = "NO NAME ", \ + .fil_sys_type = "FAT32 ", \ + }, \ + }; \ + \ + static const struct ffatdisk_config ffatdisk_config_##n = { \ + .fat_bs = &fat_bs_##n, \ + .sector_count = DT_INST_PROP(n, sector_count), \ + .fat_entries = FFAT32_ENT_IN_SECTOR(n), \ + .root_ent_cnt = FFAT16_ROOT_ENT_CNT(n), \ + .fdc = FAT32_FIRST_DATA_CLUSTER, \ + .fat1_start = FFAT32_FAT1_START, \ + .fat2_start = FFAT32_FAT2_START(n), \ + .rd_start = FFAT32_RD_START(n), \ + .data_start = FFAT32_DATA_START(n), \ + .clusters = FFAT32_CLUSTERS(n), \ + .cluster_size = FFAT_CLUSTER_SIZE(n), \ + .ffat_read = ffat_read_fat32, \ + .fat32 = true, \ + } + +#define FFATDISK_CONFIG_DEFINE(n) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_DRV_INST(n), zephyr_ffat32_disk), \ + (FFATDISK_CONFIG_FAT32_DEFINE(n)), \ + (FFATDISK_CONFIG_FAT16_DEFINE(n))) + +#define FFATDISK_DEVICE_DEFINE(n) \ + BUILD_ASSERT(DT_INST_PROP(n, sector_count) <= UINT32_MAX, \ + "Sector count is greater than UINT32_MAX"); \ + \ + FFATDISK_CONFIG_DEFINE(n); \ + \ + static struct ffatdisk_data ffatdisk_data_##n = { \ + .info = { \ + .name = DT_INST_PROP(n, disk_name), \ + .ops = &ffatdisk_ops, \ + }, \ + .vol_id = { \ + .name = "NO NAME ", \ + .attr = FAT_DIR_ATTR_VOLUME_ID, \ + }, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, ffatdisk_preinit, NULL, \ + &ffatdisk_data_##n, &ffatdisk_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &ffatdisk_ops); + +DT_INST_FOREACH_STATUS_OKAY(FFATDISK_DEVICE_DEFINE) diff --git a/drivers/disk/ffatdisk.ld b/drivers/disk/ffatdisk.ld new file mode 100644 index 000000000000..32bcfaf029d4 --- /dev/null +++ b/drivers/disk/ffatdisk.ld @@ -0,0 +1,3 @@ +#include + +ITERABLE_SECTION_RAM(ffat_file, 4) diff --git a/dts/bindings/disk/zephyr,ffat-disk.yaml b/dts/bindings/disk/zephyr,ffat-disk.yaml new file mode 100644 index 000000000000..5afecc1d3bb1 --- /dev/null +++ b/dts/bindings/disk/zephyr,ffat-disk.yaml @@ -0,0 +1,55 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: | + Fake FAT (FFAT) disk + FFAT disk emulates a disk formatted with the FAT16 or FAT32 file system to + export specific data or information to a host system, for example, using USB + MSC support. This binding is a format to describe the virtual disk geometry + as if it were a real hardware disk, so the resulting fake file system + capacity is slightly smaller. + +compatible: "zephyr,ffat-disk" + +include: base.yaml + +properties: + disk-name: + type: string + required: true + description: | + Disk name. + + sector-size: + type: int + required: true + enum: + - 512 + - 1024 + - 2048 + - 4096 + description: | + Sector size in bytes. Select it based on the granularity of the exported + data and, for example, the bootloader protocol overhead, if any. + + sector-count: + type: int + required: true + description: | + Number of sectors, it defines the capacity of the disk. For a FFAT16 disk + the allowed range is 4085 (0x0FF5) to 65524 (0xFFF4) and for a FFAT32 disk + the allowed range is 65525 (0x0000FFF5) to 268435444 (0x0FFFFFF4). + + sector-per-cluster: + type: int + required: true + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + description: | + Number of sectors per cluster. diff --git a/include/zephyr/storage/ffatdisk.h b/include/zephyr/storage/ffatdisk.h new file mode 100644 index 000000000000..34b2adf46b9a --- /dev/null +++ b/include/zephyr/storage/ffatdisk.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_STORAGE_FFATDISK +#define ZEPHYR_INCLUDE_STORAGE_FFATDISK + +#include +#include +#include + +/** + * @brief Fake FAT Disk support + * @defgroup ffat Fake FAT Disk support + * @ingroup storage_apis + * @{ + */ + +/** + * @cond INTERNAL_HIDDEN + * Internally used + */ + +#define FAT_FILE_NAME_LEN 11U + +struct ffat_file { + sys_snode_t node; + const char *disk_name; + size_t size; + uint32_t start; + uint32_t end; + int (*rd_cb)(struct ffat_file *const file, const uint32_t sector, + uint8_t *const buf, const uint32_t size); + int (*wr_cb)(struct ffat_file *const file, const uint32_t sector, + const uint8_t *const buf, const uint32_t size); + void const *priv; + char name[FAT_FILE_NAME_LEN]; + uint8_t attr; +}; + +/** @endcond */ + +/** + * @brief Macro to define the FFAT file and place it in the right memory section. + */ +#define FFAT_FILE_DEFINE(s_name, d_name, f_name, f_size, f_rd_cb, f_wr_cb, f_priv) \ + BUILD_ASSERT(f_size <= UINT32_MAX, \ + "File size is greater than UINT32_MAX"); \ + static STRUCT_SECTION_ITERABLE(ffat_file, s_name) = { \ + .name = f_name, \ + .disk_name = d_name, \ + .size = f_size, \ + .rd_cb = f_rd_cb, \ + .wr_cb = f_wr_cb, \ + .priv = f_priv, \ + } + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_STORAGE_FFATDISK */ From f1c257f36c5edf939bb01d0de9c2590826d7a558 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Sat, 23 Sep 2023 00:46:38 +0200 Subject: [PATCH 2/3] tests: drivers: disk: add test for the FFAT disk Add FAT16 and FAT32 tests for the FFAT disk using ELM FAT file system support. Signed-off-by: Johann Fischer --- tests/drivers/disk/ffat/CMakeLists.txt | 11 + tests/drivers/disk/ffat/app.overlay | 71 ++++ tests/drivers/disk/ffat/prj.conf | 12 + tests/drivers/disk/ffat/src/main.c | 520 +++++++++++++++++++++++++ tests/drivers/disk/ffat/testcase.yaml | 6 + 5 files changed, 620 insertions(+) create mode 100644 tests/drivers/disk/ffat/CMakeLists.txt create mode 100644 tests/drivers/disk/ffat/app.overlay create mode 100644 tests/drivers/disk/ffat/prj.conf create mode 100644 tests/drivers/disk/ffat/src/main.c create mode 100644 tests/drivers/disk/ffat/testcase.yaml diff --git a/tests/drivers/disk/ffat/CMakeLists.txt b/tests/drivers/disk/ffat/CMakeLists.txt new file mode 100644 index 000000000000..34b129a33242 --- /dev/null +++ b/tests/drivers/disk/ffat/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_ffat) + +target_sources(app PRIVATE + src/main.c +) diff --git a/tests/drivers/disk/ffat/app.overlay b/tests/drivers/disk/ffat/app.overlay new file mode 100644 index 000000000000..d7ed01be8f6f --- /dev/null +++ b/tests/drivers/disk/ffat/app.overlay @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ffatdisk0 { + compatible = "zephyr,ffat-disk"; + disk-name = "RAM"; + sector-size = <512>; + sector-count = <32680>; + sector-per-cluster = <1>; + }; + + ffatdisk1 { + compatible = "zephyr,ffat-disk"; + disk-name = "NAND"; + sector-size = <512>; + sector-count = <262144>; + sector-per-cluster = <4>; + }; + + ffatdisk2 { + compatible = "zephyr,ffat-disk"; + disk-name = "CF"; + sector-size = <1024>; + sector-count = <524288>; + sector-per-cluster = <8>; + }; + + ffatdisk3 { + compatible = "zephyr,ffat-disk"; + disk-name = "SD"; + sector-size = <4096>; + sector-count = <1048576>; + sector-per-cluster = <32>; + }; + + ffatdisk4 { + compatible = "zephyr,ffat32-disk", "zephyr,ffat-disk"; + disk-name = "SD2"; + sector-size = <512>; + sector-count = <131072>; + sector-per-cluster = <1>; + }; + + ffatdisk5 { + compatible = "zephyr,ffat32-disk", "zephyr,ffat-disk"; + disk-name = "USB"; + sector-size = <512>; + sector-count = <524288>; + sector-per-cluster = <4>; + }; + + ffatdisk6 { + compatible = "zephyr,ffat32-disk", "zephyr,ffat-disk"; + disk-name = "USB2"; + sector-size = <1024>; + sector-count = <1048576>; + sector-per-cluster = <8>; + }; + + ffatdisk7 { + compatible = "zephyr,ffat32-disk", "zephyr,ffat-disk"; + disk-name = "USB3"; + sector-size = <4096>; + sector-count = <16777216>; + sector-per-cluster = <64>; + }; +}; diff --git a/tests/drivers/disk/ffat/prj.conf b/tests/drivers/disk/ffat/prj.conf new file mode 100644 index 000000000000..b70dd75d77e6 --- /dev/null +++ b/tests/drivers/disk/ffat/prj.conf @@ -0,0 +1,12 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y +CONFIG_ZTEST=y + +CONFIG_DISK_ACCESS=y +CONFIG_FILE_SYSTEM=y +CONFIG_FAT_FILESYSTEM_ELM=y +CONFIG_FS_FATFS_MOUNT_MKFS=n +CONFIG_FS_FATFS_MIN_SS=512 +CONFIG_FS_FATFS_MAX_SS=4096 diff --git a/tests/drivers/disk/ffat/src/main.c b/tests/drivers/disk/ffat/src/main.c new file mode 100644 index 000000000000..b246539cffb8 --- /dev/null +++ b/tests/drivers/disk/ffat/src/main.c @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(ffat_test, LOG_LEVEL_INF); + +const char txt_test_file[] = + "Zephyr RTOS\n" + "Board " CONFIG_BOARD "\n" + "Arch "CONFIG_ARCH "\n"; + +struct binblock { + uint32_t s_tag; + uint32_t b_num; + uint8_t reserved[500]; + uint32_t e_tag; +} __packed; + +BUILD_ASSERT(sizeof(struct binblock) == 512U); + +static struct binblock last_bb; + +#define BF0_SIZE (512UL * 32408UL) +#define BF1_SIZE (2048UL * 65406UL) +#define BF2_SIZE (8192UL * 65502UL) +#define BF3_SIZE (131072UL * 32765UL) + +#define BF4_SIZE (512UL * 129006UL) +#define BF5_SIZE (2048UL * 130554UL) +#define BF6_SIZE (8192UL * 130940UL) +#define BF7_SIZE UINT32_MAX + +static int textfile_rd_cb(struct ffat_file *const f, const uint32_t sector, + uint8_t *const buf, const uint32_t size) +{ + const size_t f_off = size * sector; + + if (f->size > f_off) { + size_t len = MIN(f->size - f_off, size); + + memcpy(buf, (uint8_t *)f->priv + f_off, len); + LOG_DBG("Read %u bytes, sector %u file offset %zu, f->size %zu", + len, sector, f_off, f->size); + } else { + LOG_INF("Offset (%u) is outside of file range (%u)", f_off, f->size); + } + + return 0; +} + +static int binfile_rd_cb(struct ffat_file *const f, const uint32_t sector, + uint8_t *const buf, const uint32_t size) +{ + const size_t f_off = size * sector; + + if (f->size > f_off) { + size_t len = MIN(f->size - f_off, size); + struct binblock *bb = (void *)buf; + + bb->s_tag = sys_cpu_to_le32(0xDECAFBAD); + bb->b_num = sys_cpu_to_le32(sector); + bb->e_tag = sys_cpu_to_le32(0xDEADDA7A); + + LOG_DBG("Read %u bytes, sector %u file offset %zu, f->size %zu", + len, sector, f_off, f->size); + } else { + LOG_INF("Offset (%u) is outside of file range (%u)", f_off, f->size); + } + + return 0; +} + +static int binfile_wr_cb(struct ffat_file *const f, const uint32_t sector, + const uint8_t *const buf, const uint32_t size) +{ + size_t f_off = size * sector; + + if (f->size > f_off) { + size_t len = MIN(f->size - f_off, size); + + memcpy(&last_bb, buf, MIN(len, sizeof(last_bb))); + + LOG_DBG("Write %u bytes, sector %u file offset %zu, f->size %zu", + len, sector, f_off, f->size); + } else { + LOG_ERR("!"); + } + + return 0; +} + +FFAT_FILE_DEFINE(test1, "RAM", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test2, "RAM", "TEST_002TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test3, "RAM", "TEST_003TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test4, "RAM", "TEST_004TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test5, "RAM", "TEST_005TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test6, "RAM", "TEST_006TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test7, "RAM", "TEST_007TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test8, "RAM", "TEST_008TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test9, "RAM", "TEST_009TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test10, "RAM", "TEST_010TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test11, "RAM", "TEST_011TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test12, "RAM", "TEST_012TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test13, "RAM", "TEST_013TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test14, "RAM", "TEST_014TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); +FFAT_FILE_DEFINE(test15, "RAM", "TEST_000BIN", BF0_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); + +FFAT_FILE_DEFINE(test16, "NAND", "TEST_001BIN", BF1_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test17, "NAND", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +FFAT_FILE_DEFINE(test18, "CF", "TEST_002BIN", BF2_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test19, "CF", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +FFAT_FILE_DEFINE(test20, "SD", "TEST_003BIN", BF3_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test21, "SD", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +FFAT_FILE_DEFINE(test22, "SD2", "TEST_004BIN", BF4_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test23, "SD2", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +FFAT_FILE_DEFINE(test24, "USB", "TEST_005BIN", BF5_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test25, "USB", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +FFAT_FILE_DEFINE(test26, "USB2", "TEST_006BIN", BF6_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test27, "USB2", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +FFAT_FILE_DEFINE(test28, "USB3", "TEST_007BIN", BF7_SIZE, + binfile_rd_cb, binfile_wr_cb, NULL); +FFAT_FILE_DEFINE(test29, "USB3", "TEST_001TXT", sizeof(txt_test_file), + textfile_rd_cb, NULL, txt_test_file); + +static union { + struct binblock bb; + uint8_t a[4096]; +} ffat_test_buf; + +static FATFS fatfs[8]; + +static struct fs_mount_t mnt0 = { + .type = FS_FATFS, + .mnt_point = "/RAM:", + .fs_data = &fatfs[0], +}; + +static struct fs_mount_t mnt1 = { + .type = FS_FATFS, + .mnt_point = "/NAND:", + .fs_data = &fatfs[1], +}; + +static struct fs_mount_t mnt2 = { + .type = FS_FATFS, + .mnt_point = "/CF:", + .fs_data = &fatfs[2], +}; + +static struct fs_mount_t mnt3 = { + .type = FS_FATFS, + .mnt_point = "/SD:", + .fs_data = &fatfs[3], +}; + +static struct fs_mount_t mnt4 = { + .type = FS_FATFS, + .mnt_point = "/SD2:", + .fs_data = &fatfs[4], +}; + +static struct fs_mount_t mnt5 = { + .type = FS_FATFS, + .mnt_point = "/USB:", + .fs_data = &fatfs[5], +}; + +static struct fs_mount_t mnt6 = { + .type = FS_FATFS, + .mnt_point = "/USB2:", + .fs_data = &fatfs[6], +}; + +static struct fs_mount_t mnt7 = { + .type = FS_FATFS, + .mnt_point = "/USB3:", + .fs_data = &fatfs[7], +}; + +struct ffat_file_info { + const char *path; + fs_mode_t flags; + size_t size; +}; + +const struct ffat_file_info file_path0[] = { + {.path = "/RAM:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_002.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_003.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_004.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_005.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_006.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_007.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_008.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_009.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_010.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_011.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_012.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_013.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_014.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/RAM:/TEST_000.BIN", .flags = FS_O_RDWR, .size = BF0_SIZE,}, +}; + +const struct ffat_file_info file_path1[] = { + {.path = "/NAND:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/NAND:/TEST_001.BIN", .flags = FS_O_RDWR, .size = BF1_SIZE}, +}; + +const struct ffat_file_info file_path2[] = { + {.path = "/CF:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/CF:/TEST_002.BIN", .flags = FS_O_RDWR, .size = BF2_SIZE}, +}; + +const struct ffat_file_info file_path3[] = { + {.path = "/SD:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/SD:/TEST_003.BIN", .flags = FS_O_RDWR, .size = BF3_SIZE}, +}; + +const struct ffat_file_info file_path4[] = { + {.path = "/SD2:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/SD2:/TEST_004.BIN", .flags = FS_O_RDWR, .size = BF4_SIZE}, +}; + +const struct ffat_file_info file_path5[] = { + {.path = "/USB:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/USB:/TEST_005.BIN", .flags = FS_O_RDWR, .size = BF5_SIZE}, +}; + +const struct ffat_file_info file_path6[] = { + {.path = "/USB2:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/USB2:/TEST_006.BIN", .flags = FS_O_RDWR, .size = BF6_SIZE}, +}; + +const struct ffat_file_info file_path7[] = { + {.path = "/USB3:/TEST_001.TXT", .flags = FS_O_READ, .size = 0,}, + {.path = "/USB3:/TEST_007.BIN", .flags = FS_O_RDWR, .size = BF7_SIZE}, +}; + +static int test_mount_ffat_disk(struct fs_mount_t *const mnt) +{ + int err = 0; + + mnt->flags = FS_MOUNT_FLAG_NO_FORMAT; + err = fs_mount(mnt); + + if (err < 0) { + TC_PRINT("Failed to open ffat disk %s (%d)\n", + mnt->mnt_point, err); + return TC_FAIL; + } + + return TC_PASS; +} + +static int test_umount_ffat_disk(struct fs_mount_t *const mnt) +{ + int err = 0; + + err = fs_unmount(mnt); + + if (err < 0) { + TC_PRINT("Failed to unmount ffat disk %s (%d)\n", + mnt->mnt_point, err); + return TC_FAIL; + } + + return TC_PASS; +} + +static int ffat_test_open(struct fs_file_t *const f, + const struct ffat_file_info *const finfo) +{ + struct fs_dirent entry; + int err; + + err = fs_stat(finfo->path, &entry); + if (err) { + return TC_FAIL; + } + + err = fs_open(f, finfo->path, finfo->flags); + if (err) { + TC_PRINT("Failed to open file %s (%d)\n", finfo->path, err); + return err; + } + + return err; +} + +static int test_file_rw_bin(struct fs_file_t *const f, + const size_t fsize, const size_t bsize) +{ + const uint32_t blocks = fsize / bsize; + ssize_t len; + int err; + + err = fs_seek(f, 0, FS_SEEK_SET); + if (err) { + TC_PRINT("Failed to reset file position (%d)\n", err); + return err; + } + + TC_PRINT("File size: %u, block size: %u, blocks: %u\n", fsize, bsize, blocks); + + for (uint32_t n = 0; n < blocks; n++) { + len = fs_read(f, ffat_test_buf.a, MIN(bsize, sizeof(ffat_test_buf))); + if (len != bsize) { + TC_PRINT("Failed to read file block %u (%zd)\n", n, len); + return len; + } + + /* Verify that the file block was read successfully */ + if (sys_le32_to_cpu(ffat_test_buf.bb.s_tag) != 0xDECAFBAD || + sys_le32_to_cpu(ffat_test_buf.bb.b_num) != n || + sys_le32_to_cpu(ffat_test_buf.bb.e_tag) != 0xDEADDA7A) { + TC_PRINT("r %u: s_tag %u b_num %u e_tag %u\n", + n, + sys_le32_to_cpu(ffat_test_buf.bb.s_tag), + sys_le32_to_cpu(ffat_test_buf.bb.b_num), + sys_le32_to_cpu(ffat_test_buf.bb.e_tag)); + return TC_FAIL; + } + } + + err = fs_seek(f, 0, FS_SEEK_SET); + if (err) { + TC_PRINT("Reset file position failed (%d)\n", err); + return err; + } + + for (uint32_t n = 0; n < blocks; n++) { + ffat_test_buf.bb.s_tag = sys_cpu_to_le32(0xDEADDA7A); + ffat_test_buf.bb.b_num = sys_cpu_to_le32(n); + ffat_test_buf.bb.e_tag = sys_cpu_to_le32(0xDECAFBAD); + + len = fs_write(f, ffat_test_buf.a, MIN(bsize, sizeof(ffat_test_buf))); + if (len != bsize) { + TC_PRINT("Failed to write file block %u (%zd)\n", n, len); + return len; + } + + /* Verify that writing to the file block was successful */ + if (sys_le32_to_cpu(last_bb.s_tag) != 0xDEADDA7A || + sys_le32_to_cpu(last_bb.b_num) != n || + sys_le32_to_cpu(last_bb.e_tag) != 0xDECAFBAD) { + TC_PRINT("w %u: s_tag %u b_num %u e_tag %u\n", + n, + sys_le32_to_cpu(last_bb.s_tag), + sys_le32_to_cpu(last_bb.b_num), + sys_le32_to_cpu(last_bb.e_tag)); + return TC_FAIL; + } + } + + return TC_PASS; +} + +static int test_file_r_txt(struct fs_file_t *const f) +{ + ssize_t len; + int err; + + err = fs_seek(f, 0, FS_SEEK_SET); + if (err) { + TC_PRINT("Reset file position failed (%d)\n", err); + return err; + } + + memset(ffat_test_buf.a, 0, sizeof(ffat_test_buf)); + + len = fs_read(f, ffat_test_buf.a, sizeof(txt_test_file)); + if (len < 0) { + TC_PRINT("Failed to read txt file (%zd)\n", len); + return len; + } + + if (memcmp(txt_test_file, ffat_test_buf.a, sizeof(txt_test_file))) { + TC_PRINT("The read file differs from the original %s\n", ffat_test_buf.a); + return TC_FAIL; + } + + return TC_PASS; +} + +static int test_files_rw(struct fs_mount_t *const mnt, + const struct ffat_file_info *const finfo, const size_t size) +{ + struct fs_file_t filep; + struct fs_statvfs stat; + int err; + + err = test_mount_ffat_disk(mnt); + if (err) { + return err; + } + + err = fs_statvfs(mnt->mnt_point, &stat); + if (err) { + TC_PRINT("Failed to retrieve vfs statistics (%d)\n", err); + return err; + } + + TC_PRINT("FS block size: %u (cluster size: %u), fs blocks: %u\n", + stat.f_bsize, stat.f_frsize, stat.f_blocks); + + for (int i = 0; i < size; i++) { + memset(&filep, 0, sizeof(filep)); + + err = ffat_test_open(&filep, &finfo[i]); + if (err) { + return err; + } + + if (finfo[i].flags == FS_O_RDWR) { + err = test_file_rw_bin(&filep, finfo[i].size, stat.f_bsize); + } else { + err = test_file_r_txt(&filep); + } + + fs_close(&filep); + if (err) { + TC_PRINT("Failed on file %s\n", finfo[i].path); + return err; + } + + TC_PRINT("Test on %s passed\n", finfo[i].path); + } + + err = test_umount_ffat_disk(mnt); + if (err) { + return err; + } + + return TC_PASS; +} + +ZTEST(ffat_test, test_fat16_a) +{ + zassert_true(test_files_rw(&mnt0, file_path0, ARRAY_SIZE(file_path0)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat16_b) +{ + zassert_true(test_files_rw(&mnt1, file_path1, ARRAY_SIZE(file_path1)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat16_c) +{ + zassert_true(test_files_rw(&mnt2, file_path2, ARRAY_SIZE(file_path2)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat16_d) +{ + zassert_true(test_files_rw(&mnt3, file_path3, ARRAY_SIZE(file_path3)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat32_a) +{ + zassert_true(test_files_rw(&mnt4, file_path4, ARRAY_SIZE(file_path4)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat32_b) +{ + zassert_true(test_files_rw(&mnt5, file_path5, ARRAY_SIZE(file_path5)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat32_c) +{ + zassert_true(test_files_rw(&mnt6, file_path6, ARRAY_SIZE(file_path6)) == TC_PASS); +} + +ZTEST(ffat_test, test_fat32_d) +{ + zassert_true(test_files_rw(&mnt7, file_path7, ARRAY_SIZE(file_path7)) == TC_PASS); +} + +ZTEST_SUITE(ffat_test, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/disk/ffat/testcase.yaml b/tests/drivers/disk/ffat/testcase.yaml new file mode 100644 index 000000000000..6130dec0acb3 --- /dev/null +++ b/tests/drivers/disk/ffat/testcase.yaml @@ -0,0 +1,6 @@ +tests: + drivers.ffat: + tags: + - disk + integration_platforms: + - native_sim/native/64 From af3be53963cde776baf5990fec495484b46b44d2 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Fri, 14 Jul 2023 00:58:59 +0200 Subject: [PATCH 3/3] samples: usb: add ffatdisk sample This sample has two volumes, FAT16 and FAT32, exported via new USB device MSC support. Signed-off-by: Johann Fischer --- samples/subsys/usb/ffat/CMakeLists.txt | 10 ++ samples/subsys/usb/ffat/Kconfig | 9 ++ samples/subsys/usb/ffat/README.rst | 35 ++++++ samples/subsys/usb/ffat/app.overlay | 23 ++++ samples/subsys/usb/ffat/prj.conf | 11 ++ samples/subsys/usb/ffat/sample.yaml | 14 +++ samples/subsys/usb/ffat/src/main.c | 152 +++++++++++++++++++++++++ 7 files changed, 254 insertions(+) create mode 100644 samples/subsys/usb/ffat/CMakeLists.txt create mode 100644 samples/subsys/usb/ffat/Kconfig create mode 100644 samples/subsys/usb/ffat/README.rst create mode 100644 samples/subsys/usb/ffat/app.overlay create mode 100644 samples/subsys/usb/ffat/prj.conf create mode 100644 samples/subsys/usb/ffat/sample.yaml create mode 100644 samples/subsys/usb/ffat/src/main.c diff --git a/samples/subsys/usb/ffat/CMakeLists.txt b/samples/subsys/usb/ffat/CMakeLists.txt new file mode 100644 index 000000000000..400de7649be3 --- /dev/null +++ b/samples/subsys/usb/ffat/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ffat) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/subsys/usb/ffat/Kconfig b/samples/subsys/usb/ffat/Kconfig new file mode 100644 index 000000000000..96c545589480 --- /dev/null +++ b/samples/subsys/usb/ffat/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/subsys/usb/ffat/README.rst b/samples/subsys/usb/ffat/README.rst new file mode 100644 index 000000000000..8796d44293e2 --- /dev/null +++ b/samples/subsys/usb/ffat/README.rst @@ -0,0 +1,35 @@ +.. zephyr:code-sample:: usb-ffat + :name: USB FFAT + :relevant-api: usbd_api usbd_msc_device + + Expose a FFAT disk over USB using the USB Mass Storage Class implementation. + +Overview +******** + +This sample application demonstrates the FFAT disk implementation using the new +experimental USB device stack. The sample has two FFAT disks that emulate FAT16 +and FAT32 file system formatted disks. There is no real disk or file system +behind them. The two disks emulate FAT formatted disks at runtime. When mounted +on the host side, you will find three files on the FFAT16 disk and two files on +the FFAT32 disk. You can read and write to the binary files; the text files are +read-only. Writing to a file means that for each block written to the disk, a +callback is invoked on the Zephyr side that can be used, for example, to update +firmware or load something into the device running the Zephyr RTOS. + +Requirements +************ + +This project requires an experimental USB device driver (UDC API). + +Building and Running +******************** + +This sample can be built for multiple boards, in this example we will build it +for the nRF52840DK board: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/usb/ffat + :board: nrf52840dk/nrf52840 + :goals: build flash + :compact: diff --git a/samples/subsys/usb/ffat/app.overlay b/samples/subsys/usb/ffat/app.overlay new file mode 100644 index 000000000000..10247399a1ae --- /dev/null +++ b/samples/subsys/usb/ffat/app.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ffatdisk0 { + compatible = "zephyr,ffat-disk"; + disk-name = "FFAT16"; + sector-size = <512>; + sector-count = <8192>; + sector-per-cluster = <1>; + }; + + ffatdisk1 { + compatible = "zephyr,ffat32-disk", "zephyr,ffat-disk"; + disk-name = "FFAT32"; + sector-size = <512>; + sector-count = <7744512>; + sector-per-cluster = <8>; + }; +}; diff --git a/samples/subsys/usb/ffat/prj.conf b/samples/subsys/usb/ffat/prj.conf new file mode 100644 index 000000000000..50ef2804498b --- /dev/null +++ b/samples/subsys/usb/ffat/prj.conf @@ -0,0 +1,11 @@ +CONFIG_USB_DEVICE_STACK_NEXT=y + +CONFIG_STDOUT_CONSOLE=y +CONFIG_USBD_MSC_CLASS=y +CONFIG_USBD_MSC_LUNS_PER_INSTANCE=2 + +CONFIG_LOG=y +CONFIG_USBD_LOG_LEVEL_WRN=y +CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y + +CONFIG_MAIN_STACK_SIZE=2048 diff --git a/samples/subsys/usb/ffat/sample.yaml b/samples/subsys/usb/ffat/sample.yaml new file mode 100644 index 000000000000..d5d721f5be2a --- /dev/null +++ b/samples/subsys/usb/ffat/sample.yaml @@ -0,0 +1,14 @@ +sample: + name: USB FFAT sample +common: + depends_on: + - usbd + integration_platforms: + - nrf52840dk/nrf52840 + - nrf54h20dk/nrf54h20/cpuapp + - frdm_k64f + - nucleo_f413zh + - mimxrt1060_evk +tests: + sample.usbd.ffat: + tags: usb filesystem diff --git a/samples/subsys/usb/ffat/src/main.c b/samples/subsys/usb/ffat/src/main.c new file mode 100644 index 000000000000..18fdfabe3376 --- /dev/null +++ b/samples/subsys/usb/ffat/src/main.c @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); + +#ifdef BUILD_VERSION +#define BANNER_VERSION STRINGIFY(BUILD_VERSION) +#else +#define BANNER_VERSION KERNEL_VERSION_STRING +#endif + +const char txt_info_file[] = + "Zephyr RTOS\n" + "Build " BANNER_VERSION "\n" + "Board " CONFIG_BOARD "\n" + "Arch "CONFIG_ARCH "\n"; + +const char html_info_file[] = + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "

FFAT sample

\n" + "

zephyrproject.org

\n" + "
\n" + "\n" + "\n"; + +struct binfile { + uint32_t s_tag; + uint32_t b_num; + uint8_t reserved[500]; + uint32_t e_tag; +} __packed; + +BUILD_ASSERT(sizeof(struct binfile) == 512U); + +static int infofile_rd_cb(struct ffat_file *const f, const uint32_t sector, + uint8_t *const buf, const uint32_t size) +{ + size_t f_off = size * sector; + + if (f->size > f_off) { + size_t len = MIN(f->size - f_off, size); + + memcpy(buf, (uint8_t *)f->priv + f_off, len); + LOG_DBG("Read %u bytes, sector %u file offset %zu, f->size %zu", + len, sector, f_off, f->size); + } + + return 0; +} + +static int binfile_rd_cb(struct ffat_file *const f, const uint32_t sector, + uint8_t *const buf, const uint32_t size) +{ + size_t f_off = size * sector; + + if (f->size > f_off) { + size_t len = MIN(f->size - f_off, size); + struct binfile *test = (void *)buf; + + test->s_tag = sys_cpu_to_le32(0xDECAFBAD); + test->b_num = sys_cpu_to_le32(sector); + test->e_tag = sys_cpu_to_le32(0xDEADDA7A); + + LOG_DBG("Read %u bytes, sector %u file offset %zu, f->size %zu", + len, sector, f_off, f->size); + } + + return 0; +} + +static int binfile_wr_cb(struct ffat_file *const f, const uint32_t sector, + const uint8_t *const buf, const uint32_t size) +{ + size_t f_off = size * sector; + + if (f->size > f_off) { + size_t len = MIN(f->size - f_off, size); + + LOG_DBG("Write %u bytes, sector %u file offset %zu, f->size %zu", + len, sector, f_off, f->size); + } + + return 0; +} + +FFAT_FILE_DEFINE(readme, "FFAT16", "README TXT", sizeof(txt_info_file), + infofile_rd_cb, NULL, txt_info_file); +FFAT_FILE_DEFINE(html, "FFAT16", "INDEX HTM", sizeof(html_info_file), + infofile_rd_cb, NULL, html_info_file); +FFAT_FILE_DEFINE(bin, "FFAT16", "FOOBAZ BIN", 32768U, + binfile_rd_cb, binfile_wr_cb, NULL); + +FFAT_FILE_DEFINE(readme32, "FFAT32", "README TXT", sizeof(txt_info_file), + infofile_rd_cb, NULL, txt_info_file); +FFAT_FILE_DEFINE(big, "FFAT32", "FOOBAZ BIN", 66051072UL, + binfile_rd_cb, binfile_wr_cb, NULL); + +USBD_DEFINE_MSC_LUN(ffat16, "FFAT16", "Zephyr", "FFAT16", "0.00"); +USBD_DEFINE_MSC_LUN(ffat32, "FFAT32", "Zephyr", "FFAT32", "0.00"); + +int main(void) +{ + struct usbd_context *sample_usbd; + int ret; + + sample_usbd = sample_usbd_init_device(NULL); + if (sample_usbd == NULL) { + LOG_ERR("Failed to initialize USB device"); + return -ENODEV; + } + + ret = usbd_enable(sample_usbd); + if (ret) { + LOG_ERR("Failed to enable device support"); + return ret; + } + + LOG_INF("FFAT sample is ready."); + + return 0; +}