diff --git a/drivers/disk/CMakeLists.txt b/drivers/disk/CMakeLists.txt index 0202238a1d2ac..89178b0a677d6 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 9c1723e6e8d3d..dd9491d74298d 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 0000000000000..7f9bff2791b97 --- /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 0000000000000..a05af1766b7da --- /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 0000000000000..32bcfaf029d4a --- /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 0000000000000..5afecc1d3bb17 --- /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 0000000000000..34b2adf46b9a7 --- /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 */ diff --git a/samples/subsys/usb/ffat/CMakeLists.txt b/samples/subsys/usb/ffat/CMakeLists.txt new file mode 100644 index 0000000000000..400de7649be31 --- /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 0000000000000..96c5455894806 --- /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 0000000000000..8796d44293e2d --- /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 0000000000000..10247399a1ae3 --- /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 0000000000000..50ef2804498b4 --- /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 0000000000000..d5d721f5be2a6 --- /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 0000000000000..18fdfabe33760 --- /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; +} diff --git a/tests/drivers/disk/ffat/CMakeLists.txt b/tests/drivers/disk/ffat/CMakeLists.txt new file mode 100644 index 0000000000000..34b129a332425 --- /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 0000000000000..d7ed01be8f6f1 --- /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 0000000000000..b70dd75d77e67 --- /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 0000000000000..b246539cffb8d --- /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 0000000000000..6130dec0acb34 --- /dev/null +++ b/tests/drivers/disk/ffat/testcase.yaml @@ -0,0 +1,6 @@ +tests: + drivers.ffat: + tags: + - disk + integration_platforms: + - native_sim/native/64