diff --git a/components/fatfs/component.mk b/components/fatfs/component.mk index f2094e7ab..591e080c6 100644 --- a/components/fatfs/component.mk +++ b/components/fatfs/component.mk @@ -1,2 +1,2 @@ COMPONENT_ADD_INCLUDEDIRS := src -COMPONENT_SRCDIRS := src src/option +COMPONENT_SRCDIRS := src/option src diff --git a/components/fatfs/src/diskio.c b/components/fatfs/src/diskio.c index b00e9d2ca..004cfc5be 100644 --- a/components/fatfs/src/diskio.c +++ b/components/fatfs/src/diskio.c @@ -1,5 +1,6 @@ /*-----------------------------------------------------------------------*/ /* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */ +/* ESP-IDF port Copyright 2016 Espressif Systems (Shanghai) PTE LTD */ /*-----------------------------------------------------------------------*/ /* If a working storage control module is available, it should be */ /* attached to the FatFs via a glue function rather than modifying it. */ @@ -56,7 +57,8 @@ DWORD get_fattime(void) { time_t t = time(NULL); struct tm *tmr = gmtime(&t); - return ((DWORD)(tmr->tm_year - 80) << 25) + int year = tmr->tm_year < 80 ? 0 : tmr->tm_year - 80; + return ((DWORD)(year) << 25) | ((DWORD)(tmr->tm_mon + 1) << 21) | ((DWORD)tmr->tm_mday << 16) | (WORD)(tmr->tm_hour << 11) @@ -78,7 +80,7 @@ DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); - esp_err_t err = sdmmc_read_blocks(card, buff, sector, count); + esp_err_t err = sdmmc_read_sectors(card, buff, sector, count); if (err != ESP_OK) { ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err); return RES_ERROR; @@ -90,7 +92,7 @@ DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); - esp_err_t err = sdmmc_write_blocks(card, buff, sector, count); + esp_err_t err = sdmmc_write_sectors(card, buff, sector, count); if (err != ESP_OK) { ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err); return RES_ERROR; diff --git a/components/fatfs/src/diskio.h b/components/fatfs/src/diskio.h index 921ef9836..7c224809f 100644 --- a/components/fatfs/src/diskio.h +++ b/components/fatfs/src/diskio.h @@ -44,16 +44,36 @@ DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); +/** + * Structure of pointers to disk IO driver functions. + * + * See FatFs documentation for details about these functions + */ typedef struct { - DSTATUS (*init) (BYTE pdrv); - DSTATUS (*status) (BYTE pdrv); - DRESULT (*read) (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); - DRESULT (*write) (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); - DRESULT (*ioctl) (BYTE pdrv, BYTE cmd, void* buff); + DSTATUS (*init) (BYTE pdrv); /*!< disk initialization function */ + DSTATUS (*status) (BYTE pdrv); /*!< disk status check function */ + DRESULT (*read) (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); /*!< sector read function */ + DRESULT (*write) (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); /*!< sector write function */ + DRESULT (*ioctl) (BYTE pdrv, BYTE cmd, void* buff); /*!< function to get info about disk and do some misc operations */ } ff_diskio_impl_t; +/** + * Register diskio driver for given drive number. + * + * When FATFS library calls one of disk_xxx functions for driver number pdrv, + * corresponding function in discio_impl for given pdrv will be called. + * + * @param pdrv drive number + * @param discio_impl pointer to ff_diskio_impl_t structure with diskio functions + */ void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl); +/** + * Register SD/MMC diskio driver + * + * @param pdrv drive number + * @param card pointer to sdmmc_card_t structure describing a card; card should be initialized before calling f_mount. + */ void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card); /* Disk Status Bits (DSTATUS) */ diff --git a/components/fatfs/src/esp_vfs_fat.h b/components/fatfs/src/esp_vfs_fat.h new file mode 100644 index 000000000..087d6cf77 --- /dev/null +++ b/components/fatfs/src/esp_vfs_fat.h @@ -0,0 +1,107 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include "esp_err.h" +#include "driver/gpio.h" +#include "driver/sdmmc_types.h" +#include "driver/sdmmc_host.h" +#include "ff.h" + +/** + * @brief Register FATFS with VFS component + * + * This function registers given FAT drive in VFS, at the specified base path. + * If only one drive is used, fat_drive argument can be an empty string. + * Refer to FATFS library documentation on how to specify FAT drive. + * This function also allocates FATFS structure which should be used for f_mount + * call. + * + * @note This function doesn't mount the drive into FATFS, it just connects + * POSIX and C standard library IO function with FATFS. You need to mount + * desired drive into FATFS separately. + * + * @param base_path path prefix where FATFS should be registered + * @param fat_drive FATFS drive specification; if only one drive is used, can be an empty string + * @param max_files maximum number of files which can be open at the same time + * @param[out] out_fs pointer to FATFS structure which can be used for FATFS f_mount call is returned via this argument. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_register was already called + * - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered + */ +esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, + size_t max_files, FATFS** out_fs); + +/** + * @brief Un-register FATFS from VFS + * + * @note FATFS structure returned by esp_vfs_fat_register is destroyed after + * this call. Make sure to call f_mount function to unmount it before + * calling esp_vfs_fat_unregister. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS + */ +esp_err_t esp_vfs_fat_unregister(); + +/** + * @brief Configuration arguments for esp_vfs_fat_sdmmc_mount function + */ +typedef struct { + bool format_if_mount_failed; ///< If FAT partition can not be mounted, and this parameter is true, create partition table and format the filesystem + int max_files; ///< Max number of open files +} esp_vfs_fat_sdmmc_mount_config_t; + +/** + * @brief Convenience function to get FAT filesystem on SD card registered in VFS + * + * This is an all-in-one function which does the following: + * - initializes SD/MMC peripheral with configuration in host_config + * - initializes SD/MMC card with configuration in slot_config + * - mounts FAT partition on SD/MMC card using FATFS library, with configuration in mount_config + * - registers FATFS library with VFS, with prefix given by base_prefix variable + * + * This function is intended to make example code more compact. + * For real world applications, developers should implement the logic of + * probing SD card, locating and mounting partition, and registering FATFS in VFS, + * with proper error checking and handling of exceptional conditions. + * + * @param base_path path where partition should be registered (e.g. "/sdcard") + * @param host_config pointer to structure describing SDMMC host + * @param slot_config pointer to structure with extra SDMMC slot configuration + * @param mount_config pointer to structure with extra parameters for mounting FATFS + * @param[out] out_card if not NULL, pointer to the card information structure will be returned via this argument + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called + * - ESP_ERR_NO_MEM if memory can not be allocated + * - ESP_FAIL if partition can not be mounted + * - other error codes from SDMMC host, SDMMC protocol, or FATFS drivers + */ +esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, + const sdmmc_host_t* host_config, + const sdmmc_slot_config_t* slot_config, + const esp_vfs_fat_sdmmc_mount_config_t* mount_config, + sdmmc_card_t** out_card); + +/** + * @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_sdmmc_mount + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called + */ +esp_err_t esp_vfs_fat_sdmmc_unmount(); diff --git a/components/fatfs/src/ffconf.h b/components/fatfs/src/ffconf.h index b3a1bf574..23b63ea9e 100644 --- a/components/fatfs/src/ffconf.h +++ b/components/fatfs/src/ffconf.h @@ -241,9 +241,9 @@ / lock control is independent of re-entrancy. */ -#define _FS_REENTRANT 0 +#define _FS_REENTRANT 1 #define _FS_TIMEOUT 1000 -#define _SYNC_t HANDLE +#define _SYNC_t SemaphoreHandle_t /* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs / module itself. Note that regardless of this option, file access to different / volume is always re-entrant and volume control functions, f_mount(), f_mkfs() @@ -261,7 +261,7 @@ / SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be / included somewhere in the scope of ff.h. */ -/* #include // O/S definitions */ - +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" /*--- End of configuration options ---*/ diff --git a/components/fatfs/src/option/syscall.c b/components/fatfs/src/option/syscall.c index 14140c5a7..b6ceeaa3f 100644 --- a/components/fatfs/src/option/syscall.c +++ b/components/fatfs/src/option/syscall.c @@ -21,22 +21,8 @@ int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object _SYNC_t *sobj /* Pointer to return the created sync object */ ) { - int ret; - - - *sobj = CreateMutex(NULL, FALSE, NULL); /* Win32 */ - ret = (int)(*sobj != INVALID_HANDLE_VALUE); - -// *sobj = SyncObjects[vol]; /* uITRON (give a static sync object) */ -// ret = 1; /* The initial value of the semaphore must be 1. */ - -// *sobj = OSMutexCreate(0, &err); /* uC/OS-II */ -// ret = (int)(err == OS_NO_ERR); - -// *sobj = xSemaphoreCreateMutex(); /* FreeRTOS */ -// ret = (int)(*sobj != NULL); - - return ret; + *sobj = xSemaphoreCreateMutex(); + return (*sobj != NULL) ? 1 : 0; } @@ -53,20 +39,8 @@ int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to any erro _SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ ) { - int ret; - - - ret = CloseHandle(sobj); /* Win32 */ - -// ret = 1; /* uITRON (nothing to do) */ - -// OSMutexDel(sobj, OS_DEL_ALWAYS, &err); /* uC/OS-II */ -// ret = (int)(err == OS_NO_ERR); - -// vSemaphoreDelete(sobj); /* FreeRTOS */ -// ret = 1; - - return ret; + vSemaphoreDelete(sobj); + return 1; } @@ -82,18 +56,7 @@ int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a gran _SYNC_t sobj /* Sync object to wait */ ) { - int ret; - - ret = (int)(WaitForSingleObject(sobj, _FS_TIMEOUT) == WAIT_OBJECT_0); /* Win32 */ - -// ret = (int)(wai_sem(sobj) == E_OK); /* uITRON */ - -// OSMutexPend(sobj, _FS_TIMEOUT, &err)); /* uC/OS-II */ -// ret = (int)(err == OS_NO_ERR); - -// ret = (int)(xSemaphoreTake(sobj, _FS_TIMEOUT) == pdTRUE); /* FreeRTOS */ - - return ret; + return (xSemaphoreTake(sobj, _FS_TIMEOUT) == pdTRUE) ? 1 : 0; } @@ -108,13 +71,7 @@ void ff_rel_grant ( _SYNC_t sobj /* Sync object to be signaled */ ) { - ReleaseMutex(sobj); /* Win32 */ - -// sig_sem(sobj); /* uITRON */ - -// OSMutexPost(sobj); /* uC/OS-II */ - -// xSemaphoreGive(sobj); /* FreeRTOS */ + xSemaphoreGive(sobj); } #endif diff --git a/components/fatfs/src/vfs_fat.c b/components/fatfs/src/vfs_fat.c new file mode 100644 index 000000000..4ef387b43 --- /dev/null +++ b/components/fatfs/src/vfs_fat.c @@ -0,0 +1,538 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_log.h" +#include "ff.h" + +#include "diskio.h" + + +typedef struct { + char fat_drive[8]; + size_t max_files; + FATFS fs; + FIL files[0]; + _lock_t lock; +} vfs_fat_ctx_t; + +typedef struct { + DIR dir; + long offset; + FF_DIR ffdir; + FILINFO filinfo; + struct dirent cur_dirent; +} vfs_fat_dir_t; + + +static const char* TAG = "vfs_fat"; + +static size_t vfs_fat_write(void* p, int fd, const void * data, size_t size); +static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode); +static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size); +static int vfs_fat_open(void* ctx, const char * path, int flags, int mode); +static int vfs_fat_close(void* ctx, int fd); +static int vfs_fat_fstat(void* ctx, int fd, struct stat * st); +static int vfs_fat_stat(void* ctx, const char * path, struct stat * st); +static int vfs_fat_link(void* ctx, const char* n1, const char* n2); +static int vfs_fat_unlink(void* ctx, const char *path); +static int vfs_fat_rename(void* ctx, const char *src, const char *dst); +static DIR* vfs_fat_opendir(void* ctx, const char* name); +static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir); +static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); +static long vfs_fat_telldir(void* ctx, DIR* pdir); +static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset); +static int vfs_fat_closedir(void* ctx, DIR* pdir); +static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode); +static int vfs_fat_rmdir(void* ctx, const char* name); + + +static char s_base_path[ESP_VFS_PATH_MAX]; +static vfs_fat_ctx_t* s_fat_ctx = NULL; + +esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs) +{ + if (s_fat_ctx) { + return ESP_ERR_INVALID_STATE; + } + const esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .write_p = &vfs_fat_write, + .lseek_p = &vfs_fat_lseek, + .read_p = &vfs_fat_read, + .open_p = &vfs_fat_open, + .close_p = &vfs_fat_close, + .fstat_p = &vfs_fat_fstat, + .stat_p = &vfs_fat_stat, + .link_p = &vfs_fat_link, + .unlink_p = &vfs_fat_unlink, + .rename_p = &vfs_fat_rename, + .opendir_p = &vfs_fat_opendir, + .closedir_p = &vfs_fat_closedir, + .readdir_p = &vfs_fat_readdir, + .readdir_r_p = &vfs_fat_readdir_r, + .seekdir_p = &vfs_fat_seekdir, + .telldir_p = &vfs_fat_telldir, + .mkdir_p = &vfs_fat_mkdir, + .rmdir_p = &vfs_fat_rmdir + }; + size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL); + s_fat_ctx = (vfs_fat_ctx_t*) calloc(1, ctx_size); + if (s_fat_ctx == NULL) { + return ESP_ERR_NO_MEM; + } + s_fat_ctx->max_files = max_files; + strncpy(s_fat_ctx->fat_drive, fat_drive, sizeof(s_fat_ctx->fat_drive) - 1); + *out_fs = &s_fat_ctx->fs; + esp_err_t err = esp_vfs_register(base_path, &vfs, s_fat_ctx); + if (err != ESP_OK) { + free(s_fat_ctx); + s_fat_ctx = NULL; + return err; + } + _lock_init(&s_fat_ctx->lock); + strncpy(s_base_path, base_path, sizeof(s_base_path) - 1); + s_base_path[sizeof(s_base_path) - 1] = 0; + return ESP_OK; +} + +esp_err_t esp_vfs_fat_unregister() +{ + if (s_fat_ctx == NULL) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = esp_vfs_unregister(s_base_path); + if (err != ESP_OK) { + return err; + } + _lock_close(&s_fat_ctx->lock); + free(s_fat_ctx); + s_fat_ctx = NULL; + return ESP_OK; +} + +static int get_next_fd(vfs_fat_ctx_t* fat_ctx) +{ + for (size_t i = 0; i < fat_ctx->max_files; ++i) { + if (fat_ctx->files[i].obj.fs == NULL) { + return (int) i; + } + } + return -1; +} + +static int fat_mode_conv(int m) +{ + int res = 0; + int acc_mode = m & O_ACCMODE; + if (acc_mode == O_RDONLY) { + res |= FA_READ; + } else if (acc_mode == O_WRONLY) { + res |= FA_WRITE; + } else if (acc_mode == O_RDWR) { + res |= FA_READ | FA_WRITE; + } + if ((m & O_CREAT) && (m & O_EXCL)) { + res |= FA_CREATE_NEW; + } else if (m & O_CREAT) { + res |= FA_CREATE_ALWAYS; + } else if (m & O_APPEND) { + res |= FA_OPEN_ALWAYS; + } else { + res |= FA_OPEN_EXISTING; + } + return res; +} + +static int fresult_to_errno(FRESULT fr) +{ + switch(fr) { + case FR_DISK_ERR: return EIO; + case FR_INT_ERR: + assert(0 && "fatfs internal error"); + return EIO; + case FR_NOT_READY: return ENODEV; + case FR_NO_FILE: return ENOENT; + case FR_NO_PATH: return ENOENT; + case FR_INVALID_NAME: return EINVAL; + case FR_DENIED: return EACCES; + case FR_EXIST: return EEXIST; + case FR_INVALID_OBJECT: return EBADF; + case FR_WRITE_PROTECTED: return EACCES; + case FR_INVALID_DRIVE: return ENXIO; + case FR_NOT_ENABLED: return ENODEV; + case FR_NO_FILESYSTEM: return ENODEV; + case FR_MKFS_ABORTED: return EINTR; + case FR_TIMEOUT: return ETIMEDOUT; + case FR_LOCKED: return EACCES; + case FR_NOT_ENOUGH_CORE: return ENOMEM; + case FR_TOO_MANY_OPEN_FILES: return ENFILE; + case FR_INVALID_PARAMETER: return EINVAL; + case FR_OK: return 0; + } + assert(0 && "unhandled FRESULT"); + return ENOTSUP; +} + +static void file_cleanup(vfs_fat_ctx_t* ctx, int fd) +{ + memset(&ctx->files[fd], 0, sizeof(FIL)); +} + +static int vfs_fat_open(void* ctx, const char * path, int flags, int mode) +{ + ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&s_fat_ctx->lock); + int fd = get_next_fd(fat_ctx); + if (fd < 0) { + ESP_LOGE(TAG, "open: no free file descriptors"); + errno = ENFILE; + fd = -1; + goto out; + } + FRESULT res = f_open(&fat_ctx->files[fd], path, fat_mode_conv(flags)); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + file_cleanup(fat_ctx, fd); + errno = fresult_to_errno(res); + fd = -1; + goto out; + } +out: + _lock_release(&s_fat_ctx->lock); + return fd; +} + +static size_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + unsigned written = 0; + FRESULT res = f_write(file, data, size, &written); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + if (written == 0) { + return -1; + } + } + return written; +} + +static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + unsigned read = 0; + FRESULT res = f_read(file, dst, size, &read); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + if (read == 0) { + return -1; + } + } + return read; +} + +static int vfs_fat_close(void* ctx, int fd) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&s_fat_ctx->lock); + FIL* file = &fat_ctx->files[fd]; + FRESULT res = f_close(file); + file_cleanup(fat_ctx, fd); + int rc = 0; + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + rc = -1; + } + _lock_release(&s_fat_ctx->lock); + return rc; +} + +static off_t vfs_fat_lseek(void* ctx, int fd, off_t offset, int mode) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + off_t new_pos; + if (mode == SEEK_SET) { + new_pos = offset; + } else if (mode == SEEK_CUR) { + off_t cur_pos = f_tell(file); + new_pos = cur_pos + offset; + } else if (mode == SEEK_END) { + off_t size = f_size(file); + new_pos = size + offset; + } else { + errno = EINVAL; + return -1; + } + FRESULT res = f_lseek(file, new_pos); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return new_pos; +} + +static int vfs_fat_fstat(void* ctx, int fd, struct stat * st) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + st->st_size = f_size(file); + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG; + return 0; +} + +static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) +{ + FILINFO info; + FRESULT res = f_stat(path, &info); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + st->st_size = info.fsize; + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | + ((info.fattrib & AM_DIR) ? S_IFDIR : S_IFREG); + struct tm tm; + uint16_t fdate = info.fdate; + tm.tm_mday = fdate & 0x1f; + fdate >>= 5; + tm.tm_mon = (fdate & 0xf) - 1; + fdate >>=4; + tm.tm_year = fdate + 80; + uint16_t ftime = info.ftime; + tm.tm_sec = (ftime & 0x1f) * 2; + ftime >>= 5; + tm.tm_min = (ftime & 0x3f); + ftime >>= 6; + tm.tm_hour = (ftime & 0x1f); + st->st_mtime = mktime(&tm); + return 0; +} + +static int vfs_fat_unlink(void* ctx, const char *path) +{ + FRESULT res = f_unlink(path); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static int vfs_fat_link(void* ctx, const char* n1, const char* n2) +{ + const size_t copy_buf_size = 4096; + void* buf = malloc(copy_buf_size); + if (buf == NULL) { + errno = ENOMEM; + return -1; + } + FIL f1; + FRESULT res = f_open(&f1, n1, FA_READ | FA_OPEN_EXISTING); + if (res != FR_OK) { + goto fail1; + } + FIL f2; + res = f_open(&f2, n2, FA_WRITE | FA_CREATE_NEW); + if (res != FR_OK) { + goto fail2; + } + size_t size_left = f_size(&f1); + while (size_left > 0) { + size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size; + size_t read; + res = f_read(&f1, buf, will_copy, &read); + if (res != FR_OK) { + goto fail3; + } else if (read != will_copy) { + res = FR_DISK_ERR; + goto fail3; + } + size_t written; + res = f_write(&f2, buf, will_copy, &written); + if (res != FR_OK) { + goto fail3; + } else if (written != will_copy) { + res = FR_DISK_ERR; + goto fail3; + } + size_left -= will_copy; + } + +fail3: + f_close(&f2); +fail2: + f_close(&f1); +fail1: + free(buf); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static int vfs_fat_rename(void* ctx, const char *src, const char *dst) +{ + FRESULT res = f_rename(src, dst); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static DIR* vfs_fat_opendir(void* ctx, const char* name) +{ + vfs_fat_dir_t* fat_dir = calloc(1, sizeof(vfs_fat_dir_t)); + if (!fat_dir) { + errno = ENOMEM; + return NULL; + } + FRESULT res = f_opendir(&fat_dir->ffdir, name); + if (res != FR_OK) { + free(fat_dir); + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return NULL; + } + return (DIR*) fat_dir; +} + +static int vfs_fat_closedir(void* ctx, DIR* pdir) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + FRESULT res = f_closedir(&fat_dir->ffdir); + free(pdir); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir) +{ + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + struct dirent* out_dirent; + int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent); + if (err != 0) { + errno = err; + return NULL; + } + return out_dirent; +} + +static int vfs_fat_readdir_r(void* ctx, DIR* pdir, + struct dirent* entry, struct dirent** out_dirent) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + FRESULT res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + return fresult_to_errno(res); + } + if (fat_dir->filinfo.fname[0] == 0) { + // end of directory + *out_dirent = NULL; + return 0; + } + entry->d_ino = 0; + if (fat_dir->filinfo.fattrib & AM_DIR) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + strlcpy(entry->d_name, fat_dir->filinfo.fname, + sizeof(entry->d_name)); + fat_dir->offset++; + *out_dirent = entry; + return 0; +} + +static long vfs_fat_telldir(void* ctx, DIR* pdir) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + return fat_dir->offset; +} + +static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + FRESULT res; + if (offset < fat_dir->offset) { + res = f_rewinddir(&fat_dir->ffdir); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return; + } + fat_dir->offset = 0; + } + while (fat_dir->offset < offset) { + res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return; + } + fat_dir->offset++; + } +} + +static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode) +{ + (void) mode; + FRESULT res = f_mkdir(name); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static int vfs_fat_rmdir(void* ctx, const char* name) +{ + FRESULT res = f_unlink(name); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} diff --git a/components/fatfs/src/vfs_fat_sdmmc.c b/components/fatfs/src/vfs_fat_sdmmc.c new file mode 100644 index 000000000..e5956dd7b --- /dev/null +++ b/components/fatfs/src/vfs_fat_sdmmc.c @@ -0,0 +1,126 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "esp_log.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "driver/sdmmc_host.h" +#include "sdmmc_cmd.h" +#include "diskio.h" + +static const char* TAG = "vfs_fat_sdmmc"; +static sdmmc_card_t* s_card = NULL; + +esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, + const sdmmc_host_t* host_config, + const sdmmc_slot_config_t* slot_config, + const esp_vfs_fat_sdmmc_mount_config_t* mount_config, + sdmmc_card_t** out_card) +{ + const size_t workbuf_size = 4096; + void* workbuf = NULL; + + if (s_card != NULL) { + return ESP_ERR_INVALID_STATE; + } + // enable SDMMC + sdmmc_host_init(); + + // enable card slot + sdmmc_host_init_slot(host_config->slot, slot_config); + s_card = malloc(sizeof(sdmmc_card_t)); + if (s_card == NULL) { + return ESP_ERR_NO_MEM; + } + + // probe and initialize card + esp_err_t err = sdmmc_card_init(host_config, s_card); + if (err != ESP_OK) { + ESP_LOGD(TAG, "sdmmc_card_init failed 0x(%x)", err); + goto fail; + } + if (out_card != NULL) { + *out_card = s_card; + } + + // connect SDMMC driver to FATFS + ff_diskio_register_sdmmc(0, s_card); + + // connect FATFS to VFS + FATFS* fs; + err = esp_vfs_fat_register(base_path, "", mount_config->max_files, &fs); + if (err == ESP_ERR_INVALID_STATE) { + // it's okay, already registered with VFS + } else if (err != ESP_OK) { + ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err); + goto fail; + } + + // Try to mount partition + FRESULT res = f_mount(fs, "", 1); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGW(TAG, "failed to mount card (%d)", res); + if (!(res == FR_NO_FILESYSTEM && mount_config->format_if_mount_failed)) { + goto fail; + } + ESP_LOGW(TAG, "partitioning card"); + DWORD plist[] = {100, 0, 0, 0}; + workbuf = malloc(workbuf_size); + res = f_fdisk(0, plist, workbuf); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGD(TAG, "f_fdisk failed (%d)", res); + goto fail; + } + ESP_LOGW(TAG, "formatting card"); + res = f_mkfs("", FM_ANY, s_card->csd.sector_size, workbuf, workbuf_size); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGD(TAG, "f_mkfs failed (%d)", res); + goto fail; + } + free(workbuf); + ESP_LOGW(TAG, "mounting again"); + res = f_mount(fs, "", 0); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res); + goto fail; + } + } + return ESP_OK; + +fail: + free(workbuf); + esp_vfs_unregister(base_path); + free(s_card); + s_card = NULL; + return err; +} + +esp_err_t esp_vfs_fat_sdmmc_unmount() +{ + if (s_card == NULL) { + return ESP_ERR_INVALID_STATE; + } + // unmount + f_mount(0, "", 0); + // release SD driver + free(s_card); + s_card = NULL; + sdmmc_host_deinit(); + return esp_vfs_fat_unregister(); +} diff --git a/components/fatfs/test/component.mk b/components/fatfs/test/component.mk new file mode 100644 index 000000000..ce464a212 --- /dev/null +++ b/components/fatfs/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/fatfs/test/test_fatfs.c b/components/fatfs/test/test_fatfs.c new file mode 100644 index 000000000..9ee6606fb --- /dev/null +++ b/components/fatfs/test/test_fatfs.c @@ -0,0 +1,552 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "diskio.h" +#include "ff.h" + +static const char* hello_str = "Hello, World!\n"; + +#define HEAP_SIZE_CAPTURE() \ + size_t heap_size = esp_get_free_heap_size(); + +#define HEAP_SIZE_CHECK(tolerance) \ + do {\ + size_t final_heap_size = esp_get_free_heap_size(); \ + if (final_heap_size < heap_size - tolerance) { \ + printf("Initial heap size: %d, final: %d, diff=%d\n", heap_size, final_heap_size, heap_size - final_heap_size); \ + } \ + } while(0) + +static void create_file_with_text(const char* name, const char* text) +{ + FILE* f = fopen(name, "wb"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_TRUE(fputs(text, f) != EOF); + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +TEST_CASE("can create and write file on sd card", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + create_file_with_text("/sdcard/hello.txt", hello_str); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("can read file on sd card", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + FILE* f = fopen("/sdcard/hello.txt", "r"); + TEST_ASSERT_NOT_NULL(f); + char buf[32]; + int cb = fread(buf, 1, sizeof(buf), f); + TEST_ASSERT_EQUAL(strlen(hello_str), cb); + TEST_ASSERT_EQUAL(0, strcmp(hello_str, buf)); + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write) +{ + const size_t buf_count = file_size / buf_size; + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = write, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + FILE* f = fopen("/sdcard/4mb.bin", (write) ? "wb" : "rb"); + TEST_ASSERT_NOT_NULL(f); + + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + for (size_t n = 0; n < buf_count; ++n) { + if (write) { + TEST_ASSERT_EQUAL(1, fwrite(buf, buf_size, 1, f)); + } else { + if (fread(buf, buf_size, 1, f) != 1) { + printf("reading at n=%d, eof=%d", n, feof(f)); + TEST_FAIL(); + } + } + } + + struct timeval tv_end; + gettimeofday(&tv_end, NULL); + + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + + float t_s = tv_end.tv_sec - tv_start.tv_sec + 1e-6f * (tv_end.tv_usec - tv_start.tv_usec); + printf("%s %d bytes (block size %d) in %.3fms (%.3f MB/s)\n", + (write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3, + (file_size / 1024 / 1024) / t_s); +} + + +TEST_CASE("read speed test", "[fatfs]") +{ + + HEAP_SIZE_CAPTURE(); + const size_t buf_size = 16 * 1024; + uint32_t* buf = (uint32_t*) calloc(1, buf_size); + const size_t file_size = 4 * 1024 * 1024; + speed_test(buf, 4 * 1024, file_size, false); + HEAP_SIZE_CHECK(0); + speed_test(buf, 8 * 1024, file_size, false); + HEAP_SIZE_CHECK(0); + speed_test(buf, 16 * 1024, file_size, false); + HEAP_SIZE_CHECK(0); + free(buf); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("write speed test", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + + const size_t buf_size = 16 * 1024; + uint32_t* buf = (uint32_t*) calloc(1, buf_size); + for (size_t i = 0; i < buf_size / 4; ++i) { + buf[i] = esp_random(); + } + const size_t file_size = 4 * 1024 * 1024; + + speed_test(buf, 4 * 1024, file_size, true); + speed_test(buf, 8 * 1024, file_size, true); + speed_test(buf, 16 * 1024, file_size, true); + + free(buf); + + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("can lseek", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + FILE* f = fopen("/sdcard/seek.txt", "wb+"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); + TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR)); + TEST_ASSERT_EQUAL('9', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET)); + TEST_ASSERT_EQUAL('3', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END)); + TEST_ASSERT_EQUAL('8', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END)); + TEST_ASSERT_EQUAL(14, ftell(f)); + TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n")); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); + TEST_ASSERT_EQUAL(18, ftell(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); + char buf[20]; + TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f)); + const char ref_buf[] = "0123456789\n\0\0\0abc\n"; + TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1); + + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("stat returns correct values", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + struct tm tm; + tm.tm_year = 2016 - 1900; + tm.tm_mon = 0; + tm.tm_mday = 10; + tm.tm_hour = 16; + tm.tm_min = 30; + tm.tm_sec = 0; + time_t t = mktime(&tm); + printf("Setting time: %s", asctime(&tm)); + struct timeval now = { .tv_sec = t }; + settimeofday(&now, NULL); + + create_file_with_text("/sdcard/stat.txt", "foo\n"); + + struct stat st; + TEST_ASSERT_EQUAL(0, stat("/sdcard/stat.txt", &st)); + time_t mtime = st.st_mtime; + struct tm mtm; + localtime_r(&mtime, &mtm); + printf("File time: %s", asctime(&mtm)); + TEST_ASSERT(abs(mtime - t) < 2); // fatfs library stores time with 2 second precision + + TEST_ASSERT(st.st_mode & S_IFREG); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("unlink removes a file", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + create_file_with_text("/sdcard/unlink.txt", "unlink\n"); + + TEST_ASSERT_EQUAL(0, unlink("/sdcard/unlink.txt")); + + TEST_ASSERT_NULL(fopen("/sdcard/unlink.txt", "r")); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("link copies a file, rename moves a file", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + unlink("/sdcard/linkcopy.txt"); + unlink("/sdcard/link_dst.txt"); + unlink("/sdcard/link_src.txt"); + + FILE* f = fopen("/sdcard/link_src.txt", "w+"); + TEST_ASSERT_NOT_NULL(f); + char* str = "0123456789"; + for (int i = 0; i < 4000; ++i) { + TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f)); + } + TEST_ASSERT_EQUAL(0, fclose(f)); + + TEST_ASSERT_EQUAL(0, link("/sdcard/link_src.txt", "/sdcard/linkcopy.txt")); + + FILE* fcopy = fopen("/sdcard/linkcopy.txt", "r"); + TEST_ASSERT_NOT_NULL(fcopy); + TEST_ASSERT_EQUAL(0, fseek(fcopy, 0, SEEK_END)); + TEST_ASSERT_EQUAL(40000, ftell(fcopy)); + TEST_ASSERT_EQUAL(0, fclose(fcopy)); + + TEST_ASSERT_EQUAL(0, rename("/sdcard/linkcopy.txt", "/sdcard/link_dst.txt")); + TEST_ASSERT_NULL(fopen("/sdcard/linkcopy.txt", "r")); + FILE* fdst = fopen("/sdcard/link_dst.txt", "r"); + TEST_ASSERT_NOT_NULL(fdst); + TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END)); + TEST_ASSERT_EQUAL(40000, ftell(fdst)); + TEST_ASSERT_EQUAL(0, fclose(fdst)); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +typedef struct { + const char* filename; + bool write; + size_t word_count; + int seed; + SemaphoreHandle_t done; + int result; +} read_write_test_arg_t; + +#define READ_WRITE_TEST_ARG_INIT(name, seed_) \ + { \ + .filename = name, \ + .seed = seed_, \ + .word_count = 8192, \ + .write = true, \ + .done = xSemaphoreCreateBinary() \ + } + +static void read_write_task(void* param) +{ + read_write_test_arg_t* args = (read_write_test_arg_t*) param; + FILE* f = fopen(args->filename, args->write ? "wb" : "rb"); + if (f == NULL) { + args->result = ESP_ERR_NOT_FOUND; + goto done; + } + + srand(args->seed); + for (size_t i = 0; i < args->word_count; ++i) { + uint32_t val = rand(); + if (args->write) { + int cnt = fwrite(&val, sizeof(val), 1, f); + if (cnt != 1) { + args->result = ESP_FAIL; + goto close; + } + } else { + uint32_t rval; + int cnt = fread(&rval, sizeof(rval), 1, f); + if (cnt != 1 || rval != val) { + ets_printf("E: i=%d, cnt=%d rval=%d val=%d\n\n", i, cnt, rval, val); + args->result = ESP_FAIL; + goto close; + } + } + } + args->result = ESP_OK; + +close: + fclose(f); + +done: + xSemaphoreGive(args->done); + vTaskDelay(1); + vTaskDelete(NULL); +} + + +TEST_CASE("multiple tasks can use same volume", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT("/sdcard/f1", 1); + read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT("/sdcard/f2", 2); + + printf("writing f1 and f2\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); + + xSemaphoreTake(args1.done, portMAX_DELAY); + printf("f1 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args1.result); + xSemaphoreTake(args2.done, portMAX_DELAY); + printf("f2 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args2.result); + + args1.write = false; + args2.write = false; + read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT("/sdcard/f3", 3); + read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT("/sdcard/f4", 4); + + printf("reading f1 and f2, writing f3 and f4\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, 1); + xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); + + xSemaphoreTake(args1.done, portMAX_DELAY); + printf("f1 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args1.result); + xSemaphoreTake(args2.done, portMAX_DELAY); + printf("f2 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args2.result); + xSemaphoreTake(args3.done, portMAX_DELAY); + printf("f3 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args3.result); + xSemaphoreTake(args4.done, portMAX_DELAY); + printf("f4 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args4.result); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + vSemaphoreDelete(args1.done); + vSemaphoreDelete(args2.done); + vSemaphoreDelete(args3.done); + vSemaphoreDelete(args4.done); + vTaskDelay(10); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("can create and remove directories", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir1", 0755)); + struct stat st; + TEST_ASSERT_EQUAL(0, stat("/sdcard/dir1", &st)); + TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); + TEST_ASSERT_FALSE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir1")); + TEST_ASSERT_EQUAL(-1, stat("/sdcard/dir1", &st)); + + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir2", 0755)); + create_file_with_text("/sdcard/dir2/1.txt", "foo\n"); + TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2", &st)); + TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); + TEST_ASSERT_FALSE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2/1.txt", &st)); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); + TEST_ASSERT_TRUE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(-1, rmdir("/sdcard/dir2")); + TEST_ASSERT_EQUAL(0, unlink("/sdcard/dir2/1.txt")); + TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir2")); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + unlink("/sdcard/dir/inner/3.txt"); + rmdir("/sdcard/dir/inner"); + unlink("/sdcard/dir/2.txt"); + unlink("/sdcard/dir/1.txt"); + unlink("/sdcard/dir/boo.bin"); + rmdir("/sdcard/dir"); + + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir", 0755)); + create_file_with_text("/sdcard/dir/2.txt", "1\n"); + create_file_with_text("/sdcard/dir/1.txt", "1\n"); + create_file_with_text("/sdcard/dir/boo.bin", "\01\02\03"); + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir/inner", 0755)); + create_file_with_text("/sdcard/dir/inner/3.txt", "3\n"); + + DIR* dir = opendir("/sdcard/dir"); + TEST_ASSERT_NOT_NULL(dir); + int count = 0; + const char* names[4]; + while(count < 4) { + struct dirent* de = readdir(dir); + if (!de) { + break; + } + printf("found '%s'\n", de->d_name); + if (strcasecmp(de->d_name, "1.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "1.txt"; + ++count; + } else if (strcasecmp(de->d_name, "2.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "2.txt"; + ++count; + } else if (strcasecmp(de->d_name, "inner") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_DIR); + names[count] = "inner"; + ++count; + } else if (strcasecmp(de->d_name, "boo.bin") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "boo.bin"; + ++count; + } else { + TEST_FAIL_MESSAGE("unexpected directory entry"); + } + } + TEST_ASSERT_EQUAL(count, 4); + + rewinddir(dir); + struct dirent* de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0])); + seekdir(dir, 3); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3])); + seekdir(dir, 1); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1])); + seekdir(dir, 2); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2])); + + TEST_ASSERT_EQUAL(0, closedir(dir)); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} diff --git a/components/newlib/test/test_newlib.c b/components/newlib/test/test_newlib.c index 1d86cd4a9..1d2a4bf09 100644 --- a/components/newlib/test/test_newlib.c +++ b/components/newlib/test/test_newlib.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include "unity.h" #include "sdkconfig.h" @@ -86,6 +88,33 @@ TEST_CASE("test time functions", "[newlib]") } +TEST_CASE("test asctime", "[newlib]") +{ + char buf[64]; + struct tm tm = { 0 }; + tm.tm_year = 2016 - 1900; + tm.tm_mon = 0; + tm.tm_mday = 10; + tm.tm_hour = 16; + tm.tm_min = 30; + tm.tm_sec = 0; + time_t t = mktime(&tm); + const char* time_str = asctime(&tm); + strlcpy(buf, time_str, sizeof(buf)); + printf("Setting time: %s", time_str); + struct timeval now = { .tv_sec = t }; + settimeofday(&now, NULL); + + struct timeval tv; + gettimeofday(&tv, NULL); + time_t mtime = tv.tv_sec; + struct tm mtm; + localtime_r(&mtime, &mtm); + time_str = asctime(&mtm); + printf("Got time: %s", time_str); + TEST_ASSERT_EQUAL_STRING(buf, time_str); +} + static bool fn_in_rom(void *fn, char *name) { const int fnaddr = (int)fn; diff --git a/components/sdmmc/test/test_sd.c b/components/sdmmc/test/test_sd.c index e8fbd2545..6c2154817 100644 --- a/components/sdmmc/test/test_sd.c +++ b/components/sdmmc/test/test_sd.c @@ -1,3 +1,17 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include