fork the esp-idf fatfs for f_forward and exfat support
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/fatfs/test_apps/sdcard:
|
||||
disable_test:
|
||||
- if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c6", "esp32h2"]
|
||||
temporary: true
|
||||
reason: No sdspi runners for these targets
|
||||
@@ -0,0 +1,16 @@
|
||||
set(srcs "diskio/diskio.c"
|
||||
"diskio/diskio_rawflash.c"
|
||||
"diskio/diskio_sdmmc.c"
|
||||
"diskio/diskio_wl.c"
|
||||
"src/ff.c"
|
||||
"port/freertos/ffsystem.c"
|
||||
"src/ffunicode.c"
|
||||
"vfs/vfs_fat.c"
|
||||
"vfs/vfs_fat_sdmmc.c"
|
||||
"vfs/vfs_fat_spiflash.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS diskio vfs src
|
||||
REQUIRES wear_levelling sdmmc
|
||||
PRIV_REQUIRES vfs
|
||||
)
|
||||
@@ -0,0 +1,233 @@
|
||||
menu "FAT Filesystem support"
|
||||
|
||||
config FATFS_VOLUME_COUNT
|
||||
int "Number of volumes"
|
||||
default 2
|
||||
range 1 10
|
||||
help
|
||||
Number of volumes (logical drives) to use.
|
||||
|
||||
choice FATFS_LONG_FILENAMES
|
||||
prompt "Long filename support"
|
||||
default FATFS_LFN_NONE
|
||||
help
|
||||
Support long filenames in FAT. Long filename data increases
|
||||
memory usage. FATFS can be configured to store the buffer for
|
||||
long filename data in stack or heap.
|
||||
|
||||
config FATFS_LFN_NONE
|
||||
bool "No long filenames"
|
||||
config FATFS_LFN_HEAP
|
||||
bool "Long filename buffer in heap"
|
||||
config FATFS_LFN_STACK
|
||||
bool "Long filename buffer on stack"
|
||||
endchoice
|
||||
choice FATFS_SECTOR_SIZE
|
||||
prompt "Sector size"
|
||||
default FATFS_SECTOR_4096
|
||||
help
|
||||
Specify the size of the sector in bytes for FATFS partition generator.
|
||||
|
||||
config FATFS_SECTOR_512
|
||||
bool "512"
|
||||
config FATFS_SECTOR_4096
|
||||
bool "4096"
|
||||
endchoice
|
||||
choice FATFS_CHOOSE_CODEPAGE
|
||||
prompt "OEM Code Page"
|
||||
default FATFS_CODEPAGE_437
|
||||
help
|
||||
OEM code page used for file name encodings.
|
||||
|
||||
If "Dynamic" is selected, code page can be chosen at runtime using
|
||||
f_setcp function. Note that choosing this option will increase
|
||||
application size by ~480kB.
|
||||
|
||||
config FATFS_CODEPAGE_DYNAMIC
|
||||
bool "Dynamic (all code pages supported)"
|
||||
config FATFS_CODEPAGE_437
|
||||
bool "US (CP437)"
|
||||
config FATFS_CODEPAGE_720
|
||||
bool "Arabic (CP720)"
|
||||
config FATFS_CODEPAGE_737
|
||||
bool "Greek (CP737)"
|
||||
config FATFS_CODEPAGE_771
|
||||
bool "KBL (CP771)"
|
||||
config FATFS_CODEPAGE_775
|
||||
bool "Baltic (CP775)"
|
||||
config FATFS_CODEPAGE_850
|
||||
bool "Latin 1 (CP850)"
|
||||
config FATFS_CODEPAGE_852
|
||||
bool "Latin 2 (CP852)"
|
||||
config FATFS_CODEPAGE_855
|
||||
bool "Cyrillic (CP855)"
|
||||
config FATFS_CODEPAGE_857
|
||||
bool "Turkish (CP857)"
|
||||
config FATFS_CODEPAGE_860
|
||||
bool "Portugese (CP860)"
|
||||
config FATFS_CODEPAGE_861
|
||||
bool "Icelandic (CP861)"
|
||||
config FATFS_CODEPAGE_862
|
||||
bool "Hebrew (CP862)"
|
||||
config FATFS_CODEPAGE_863
|
||||
bool "Canadian French (CP863)"
|
||||
config FATFS_CODEPAGE_864
|
||||
bool "Arabic (CP864)"
|
||||
config FATFS_CODEPAGE_865
|
||||
bool "Nordic (CP865)"
|
||||
config FATFS_CODEPAGE_866
|
||||
bool "Russian (CP866)"
|
||||
config FATFS_CODEPAGE_869
|
||||
bool "Greek 2 (CP869)"
|
||||
config FATFS_CODEPAGE_932
|
||||
bool "Japanese (DBCS) (CP932)"
|
||||
config FATFS_CODEPAGE_936
|
||||
bool "Simplified Chinese (DBCS) (CP936)"
|
||||
config FATFS_CODEPAGE_949
|
||||
bool "Korean (DBCS) (CP949)"
|
||||
config FATFS_CODEPAGE_950
|
||||
bool "Traditional Chinese (DBCS) (CP950)"
|
||||
|
||||
endchoice
|
||||
config FATFS_CODEPAGE
|
||||
int
|
||||
default 0 if FATFS_CODEPAGE_DYNAMIC
|
||||
default 437 if FATFS_CODEPAGE_437
|
||||
default 720 if FATFS_CODEPAGE_720
|
||||
default 737 if FATFS_CODEPAGE_737
|
||||
default 771 if FATFS_CODEPAGE_771
|
||||
default 775 if FATFS_CODEPAGE_775
|
||||
default 850 if FATFS_CODEPAGE_850
|
||||
default 852 if FATFS_CODEPAGE_852
|
||||
default 855 if FATFS_CODEPAGE_855
|
||||
default 857 if FATFS_CODEPAGE_857
|
||||
default 860 if FATFS_CODEPAGE_860
|
||||
default 861 if FATFS_CODEPAGE_861
|
||||
default 862 if FATFS_CODEPAGE_862
|
||||
default 863 if FATFS_CODEPAGE_863
|
||||
default 864 if FATFS_CODEPAGE_864
|
||||
default 865 if FATFS_CODEPAGE_865
|
||||
default 866 if FATFS_CODEPAGE_866
|
||||
default 869 if FATFS_CODEPAGE_869
|
||||
default 932 if FATFS_CODEPAGE_932
|
||||
default 936 if FATFS_CODEPAGE_936
|
||||
default 949 if FATFS_CODEPAGE_949
|
||||
default 950 if FATFS_CODEPAGE_950
|
||||
default 437
|
||||
|
||||
config FATFS_MAX_LFN
|
||||
int "Max long filename length"
|
||||
depends on !FATFS_LFN_NONE
|
||||
default 255
|
||||
range 12 255
|
||||
help
|
||||
Maximum long filename length. Can be reduced to save RAM.
|
||||
|
||||
choice FATFS_API_ENCODING
|
||||
prompt "API character encoding"
|
||||
depends on !FATFS_LFN_NONE
|
||||
default FATFS_API_ENCODING_ANSI_OEM
|
||||
help
|
||||
Choose encoding for character and string arguments/returns when using
|
||||
FATFS APIs. The encoding of arguments will usually depend on text
|
||||
editor settings.
|
||||
|
||||
config FATFS_API_ENCODING_ANSI_OEM
|
||||
bool "API uses ANSI/OEM encoding"
|
||||
config FATFS_API_ENCODING_UTF_8
|
||||
bool "API uses UTF-8 encoding"
|
||||
endchoice
|
||||
|
||||
config FATFS_FS_LOCK
|
||||
int "Number of simultaneously open files protected by lock function"
|
||||
default 0
|
||||
range 0 65535
|
||||
help
|
||||
This option sets the FATFS configuration value _FS_LOCK.
|
||||
The option _FS_LOCK switches file lock function to control duplicated file open
|
||||
and illegal operation to open objects.
|
||||
|
||||
* 0: Disable file lock function. To avoid volume corruption, application
|
||||
should avoid illegal open, remove and rename to the open objects.
|
||||
|
||||
* >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
can be opened simultaneously under file lock control.
|
||||
|
||||
Note that the file lock control is independent of re-entrancy.
|
||||
|
||||
config FATFS_TIMEOUT_MS
|
||||
int "Timeout for acquiring a file lock, ms"
|
||||
default 10000
|
||||
help
|
||||
This option sets FATFS configuration value _FS_TIMEOUT, scaled to milliseconds.
|
||||
Sets the number of milliseconds FATFS will wait to acquire a mutex when
|
||||
operating on an open file. For example, if one task is performing a lenghty
|
||||
operation, another task will wait for the first task to release the lock,
|
||||
and time out after amount of time set by this option.
|
||||
|
||||
|
||||
config FATFS_PER_FILE_CACHE
|
||||
bool "Use separate cache for each file"
|
||||
default y
|
||||
help
|
||||
This option affects FATFS configuration value _FS_TINY.
|
||||
|
||||
If this option is set, _FS_TINY is 0, and each open file has its own cache,
|
||||
size of the cache is equal to the _MAX_SS variable (512 or 4096 bytes).
|
||||
This option uses more RAM if more than 1 file is open, but needs less reads
|
||||
and writes to the storage for some operations.
|
||||
|
||||
If this option is not set, _FS_TINY is 1, and single cache is used for
|
||||
all open files, size is also equal to _MAX_SS variable. This reduces the
|
||||
amount of heap used when multiple files are open, but increases the number
|
||||
of read and write operations which FATFS needs to make.
|
||||
|
||||
|
||||
config FATFS_ALLOC_PREFER_EXTRAM
|
||||
bool "Perfer external RAM when allocating FATFS buffers"
|
||||
default y
|
||||
depends on SPIRAM_USE_CAPS_ALLOC || SPIRAM_USE_MALLOC
|
||||
help
|
||||
When the option is enabled, internal buffers used by FATFS will be allocated
|
||||
from external RAM. If the allocation from external RAM fails, the buffer will
|
||||
be allocated from the internal RAM.
|
||||
Disable this option if optimizing for performance. Enable this option if
|
||||
optimizing for internal memory size.
|
||||
|
||||
|
||||
config FATFS_USE_FASTSEEK
|
||||
bool "Enable fast seek algorithm when using lseek function through VFS FAT"
|
||||
default n
|
||||
help
|
||||
The fast seek feature enables fast backward/long seek operations without
|
||||
FAT access by using an in-memory CLMT (cluster link map table).
|
||||
Please note, fast-seek is only allowed for read-mode files, if a
|
||||
file is opened in write-mode, the seek mechanism will automatically fallback
|
||||
to the default implementation.
|
||||
|
||||
|
||||
config FATFS_FAST_SEEK_BUFFER_SIZE
|
||||
int "Fast seek CLMT buffer size"
|
||||
default 64
|
||||
depends on FATFS_USE_FASTSEEK
|
||||
help
|
||||
If fast seek algorithm is enabled, this defines the size of
|
||||
CLMT buffer used by this algorithm in 32-bit word units.
|
||||
This value should be chosen based on prior knowledge of
|
||||
maximum elements of each file entry would store.
|
||||
|
||||
config FATFS_VFS_FSTAT_BLKSIZE
|
||||
int "Default block size"
|
||||
default 0
|
||||
help
|
||||
If set to 0, the 'newlib' library's default size (BLKSIZ) is used (128 B).
|
||||
If set to a non-zero value, the value is used as the block size.
|
||||
Default file buffer size is set to this value
|
||||
and the buffer is allocated when first attempt of reading/writing to a file is made.
|
||||
Increasing this value improves fread() speed, however the heap usage is increased as well.
|
||||
|
||||
NOTE: The block size value is shared by all the filesystem functions
|
||||
accessing target media for given file descriptor!
|
||||
See 'Improving I/O performance' section of 'Maximizing Execution Speed' documentation page
|
||||
for more details.
|
||||
endmenu
|
||||
@@ -0,0 +1,117 @@
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* 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. */
|
||||
/* This is an example of glue functions to attach various exsisting */
|
||||
/* storage control modules to the FatFs module with a defined API. */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include "diskio_impl.h"
|
||||
#include "ffconf.h"
|
||||
#include "ff.h"
|
||||
|
||||
static ff_diskio_impl_t * s_impls[FF_VOLUMES] = { NULL };
|
||||
|
||||
#if FF_MULTI_PARTITION /* Multiple partition configuration */
|
||||
const PARTITION VolToPart[FF_VOLUMES] = {
|
||||
{0, 0}, /* Logical drive 0 ==> Physical drive 0, auto detection */
|
||||
{1, 0}, /* Logical drive 1 ==> Physical drive 1, auto detection */
|
||||
#if FF_VOLUMES > 2
|
||||
{2, 0}, /* Logical drive 2 ==> Physical drive 2, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 3
|
||||
{3, 0}, /* Logical drive 3 ==> Physical drive 3, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 4
|
||||
{4, 0}, /* Logical drive 4 ==> Physical drive 4, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 5
|
||||
{5, 0}, /* Logical drive 5 ==> Physical drive 5, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 6
|
||||
{6, 0}, /* Logical drive 6 ==> Physical drive 6, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 7
|
||||
{7, 0}, /* Logical drive 7 ==> Physical drive 7, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 8
|
||||
{8, 0}, /* Logical drive 8 ==> Physical drive 8, auto detection */
|
||||
#endif
|
||||
#if FF_VOLUMES > 9
|
||||
{9, 0}, /* Logical drive 9 ==> Physical drive 9, auto detection */
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
esp_err_t ff_diskio_get_drive(BYTE* out_pdrv)
|
||||
{
|
||||
BYTE i;
|
||||
for(i=0; i<FF_VOLUMES; i++) {
|
||||
if (!s_impls[i]) {
|
||||
*out_pdrv = i;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl)
|
||||
{
|
||||
assert(pdrv < FF_VOLUMES);
|
||||
|
||||
if (s_impls[pdrv]) {
|
||||
ff_diskio_impl_t* im = s_impls[pdrv];
|
||||
s_impls[pdrv] = NULL;
|
||||
free(im);
|
||||
}
|
||||
|
||||
if (!discio_impl) {
|
||||
return;
|
||||
}
|
||||
|
||||
ff_diskio_impl_t * impl = (ff_diskio_impl_t *)malloc(sizeof(ff_diskio_impl_t));
|
||||
assert(impl != NULL);
|
||||
memcpy(impl, discio_impl, sizeof(ff_diskio_impl_t));
|
||||
s_impls[pdrv] = impl;
|
||||
}
|
||||
|
||||
DSTATUS ff_disk_initialize (BYTE pdrv)
|
||||
{
|
||||
return s_impls[pdrv]->init(pdrv);
|
||||
}
|
||||
DSTATUS ff_disk_status (BYTE pdrv)
|
||||
{
|
||||
return s_impls[pdrv]->status(pdrv);
|
||||
}
|
||||
DRESULT ff_disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count)
|
||||
{
|
||||
return s_impls[pdrv]->read(pdrv, buff, sector, count);
|
||||
}
|
||||
DRESULT ff_disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count)
|
||||
{
|
||||
return s_impls[pdrv]->write(pdrv, buff, sector, count);
|
||||
}
|
||||
DRESULT ff_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff)
|
||||
{
|
||||
return s_impls[pdrv]->ioctl(pdrv, cmd, buff);
|
||||
}
|
||||
|
||||
DWORD get_fattime(void)
|
||||
{
|
||||
time_t t = time(NULL);
|
||||
struct tm tmr;
|
||||
localtime_r(&t, &tmr);
|
||||
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)
|
||||
| (WORD)(tmr.tm_min << 5)
|
||||
| (WORD)(tmr.tm_sec >> 1);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2019 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
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
typedef unsigned int UINT;
|
||||
typedef unsigned char BYTE;
|
||||
typedef uint32_t DWORD;
|
||||
|
||||
#define FF_DRV_NOT_USED 0xFF
|
||||
|
||||
#include "diskio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* Structure of pointers to disk IO driver functions.
|
||||
*
|
||||
* See FatFs documentation for details about these functions
|
||||
*/
|
||||
typedef struct {
|
||||
DSTATUS (*init) (unsigned char pdrv); /*!< disk initialization function */
|
||||
DSTATUS (*status) (unsigned char pdrv); /*!< disk status check function */
|
||||
DRESULT (*read) (unsigned char pdrv, unsigned char* buff, uint32_t sector, unsigned count); /*!< sector read function */
|
||||
DRESULT (*write) (unsigned char pdrv, const unsigned char* buff, uint32_t sector, unsigned count); /*!< sector write function */
|
||||
DRESULT (*ioctl) (unsigned char pdrv, unsigned char cmd, void* buff); /*!< function to get info about disk and do some misc operations */
|
||||
} ff_diskio_impl_t;
|
||||
|
||||
/**
|
||||
* Register or unregister 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
|
||||
* or NULL to unregister and free previously registered drive
|
||||
*/
|
||||
void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl);
|
||||
|
||||
#define ff_diskio_unregister(pdrv_) ff_diskio_register(pdrv_, NULL)
|
||||
|
||||
|
||||
/**
|
||||
* Get next available drive number
|
||||
*
|
||||
* @param out_pdrv pointer to the byte to set if successful
|
||||
*
|
||||
* @return ESP_OK on success
|
||||
* ESP_ERR_NOT_FOUND if all drives are attached
|
||||
*/
|
||||
esp_err_t ff_diskio_get_drive(BYTE* out_pdrv);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "diskio_impl.h"
|
||||
#include "ffconf.h"
|
||||
#include "ff.h"
|
||||
#include "esp_log.h"
|
||||
#include "diskio_rawflash.h"
|
||||
#include "esp_compiler.h"
|
||||
|
||||
static const char* TAG = "diskio_rawflash";
|
||||
|
||||
static const esp_partition_t* s_ff_raw_handles[FF_VOLUMES];
|
||||
// Determine the sector size and sector count by parsing the boot sector
|
||||
static size_t s_sector_size[FF_VOLUMES];
|
||||
static size_t s_sectors_count[FF_VOLUMES];
|
||||
|
||||
#define BPB_BytsPerSec 11
|
||||
#define BPB_TotSec16 19
|
||||
#define BPB_TotSec32 32
|
||||
|
||||
|
||||
DSTATUS ff_raw_initialize (BYTE pdrv)
|
||||
{
|
||||
|
||||
uint16_t sector_size_tmp;
|
||||
uint16_t sectors_count_tmp_16;
|
||||
uint32_t sectors_count_tmp_32;
|
||||
|
||||
const esp_partition_t* part = s_ff_raw_handles[pdrv];
|
||||
assert(part);
|
||||
esp_err_t err = esp_partition_read(part, BPB_BytsPerSec, §or_size_tmp, sizeof(sector_size_tmp));
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "esp_partition_read failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
s_sector_size[pdrv] = sector_size_tmp;
|
||||
|
||||
err = esp_partition_read(part, BPB_TotSec16, §ors_count_tmp_16, sizeof(sectors_count_tmp_16));
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "esp_partition_read failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
s_sectors_count[pdrv] = sectors_count_tmp_16;
|
||||
// For FAT32, the number of sectors is stored in a different field
|
||||
if (sectors_count_tmp_16 == 0){
|
||||
err = esp_partition_read(part, BPB_TotSec32, §ors_count_tmp_32, sizeof(sectors_count_tmp_32));
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "esp_partition_read failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
s_sectors_count[pdrv] = sectors_count_tmp_32;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DSTATUS ff_raw_status (BYTE pdrv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
ESP_LOGV(TAG, "ff_raw_read - pdrv=%i, sector=%i, count=%in", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
|
||||
const esp_partition_t* part = s_ff_raw_handles[pdrv];
|
||||
assert(part);
|
||||
esp_err_t err = esp_partition_read(part, sector * s_sector_size[pdrv], buff, count * s_sector_size[pdrv]);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "esp_partition_read failed (0x%x)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
|
||||
DRESULT ff_raw_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
DRESULT ff_raw_ioctl (BYTE pdrv, BYTE cmd, void *buff)
|
||||
{
|
||||
const esp_partition_t* part = s_ff_raw_handles[pdrv];
|
||||
ESP_LOGV(TAG, "ff_raw_ioctl: cmd=%in", cmd);
|
||||
assert(part);
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
return RES_OK;
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *) buff) = s_sectors_count[pdrv];
|
||||
return RES_OK;
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *) buff) = s_sector_size[pdrv];
|
||||
return RES_OK;
|
||||
case GET_BLOCK_SIZE:
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t ff_diskio_register_raw_partition(BYTE pdrv, const esp_partition_t* part_handle)
|
||||
{
|
||||
if (pdrv >= FF_VOLUMES) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
static const ff_diskio_impl_t raw_impl = {
|
||||
.init = &ff_raw_initialize,
|
||||
.status = &ff_raw_status,
|
||||
.read = &ff_raw_read,
|
||||
.write = &ff_raw_write,
|
||||
.ioctl = &ff_raw_ioctl
|
||||
};
|
||||
ff_diskio_register(pdrv, &raw_impl);
|
||||
s_ff_raw_handles[pdrv] = part_handle;
|
||||
return ESP_OK;
|
||||
|
||||
}
|
||||
|
||||
|
||||
BYTE ff_diskio_get_pdrv_raw(const esp_partition_t* part_handle)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (part_handle == s_ff_raw_handles[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015-2018 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.
|
||||
|
||||
#ifndef _DISKIO_RAWFLASH_DEFINED
|
||||
#define _DISKIO_RAWFLASH_DEFINED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_partition.h"
|
||||
|
||||
/**
|
||||
* Register spi flash partition
|
||||
*
|
||||
* @param pdrv drive number
|
||||
* @param part_handle pointer to raw flash partition.
|
||||
*/
|
||||
esp_err_t ff_diskio_register_raw_partition(unsigned char pdrv, const esp_partition_t* part_handle);
|
||||
unsigned char ff_diskio_get_pdrv_raw(const esp_partition_t* part_handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _DISKIO_RAWFLASH_DEFINED
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "diskio_impl.h"
|
||||
#include "ffconf.h"
|
||||
#include "ff.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_compiler.h"
|
||||
|
||||
static sdmmc_card_t* s_cards[FF_VOLUMES] = { NULL };
|
||||
static bool s_disk_status_check_en[FF_VOLUMES] = { };
|
||||
|
||||
static const char* TAG = "diskio_sdmmc";
|
||||
|
||||
//Check if SD/MMC card is present
|
||||
static DSTATUS ff_sdmmc_card_available(BYTE pdrv)
|
||||
{
|
||||
sdmmc_card_t* card = s_cards[pdrv];
|
||||
assert(card);
|
||||
esp_err_t err = sdmmc_get_status(card);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "Check status failed (0x%x)", err);
|
||||
return STA_NOINIT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ff_sdmmc_status() and ff_sdmmc_initialize() return STA_NOINIT when sdmmc_get_status()
|
||||
* fails. This error value is checked throughout the FATFS code.
|
||||
* Both functions return 0 on success.
|
||||
*/
|
||||
DSTATUS ff_sdmmc_initialize (BYTE pdrv)
|
||||
{
|
||||
return ff_sdmmc_card_available(pdrv);
|
||||
}
|
||||
|
||||
DSTATUS ff_sdmmc_status(BYTE pdrv)
|
||||
{
|
||||
if (s_disk_status_check_en[pdrv]) {
|
||||
return ff_sdmmc_card_available(pdrv);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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_sectors(card, buff, sector, count);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
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_sectors(card, buff, sector, count);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#if FF_USE_TRIM
|
||||
DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count)
|
||||
{
|
||||
sdmmc_card_t* card = s_cards[pdrv];
|
||||
assert(card);
|
||||
sdmmc_erase_arg_t arg;
|
||||
|
||||
arg = sdmmc_can_discard(card) == ESP_OK ? SDMMC_DISCARD_ARG : SDMMC_ERASE_ARG;
|
||||
esp_err_t err = sdmmc_erase_sectors(card, start_sector, sector_count, arg);
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "sdmmc_erase_sectors failed (%d)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
#endif //FF_USE_TRIM
|
||||
|
||||
DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff)
|
||||
{
|
||||
sdmmc_card_t* card = s_cards[pdrv];
|
||||
assert(card);
|
||||
switch(cmd) {
|
||||
case CTRL_SYNC:
|
||||
return RES_OK;
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD*) buff) = card->csd.capacity;
|
||||
return RES_OK;
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD*) buff) = card->csd.sector_size;
|
||||
return RES_OK;
|
||||
case GET_BLOCK_SIZE:
|
||||
return RES_ERROR;
|
||||
#if FF_USE_TRIM
|
||||
case CTRL_TRIM:
|
||||
return ff_sdmmc_trim (pdrv, *((DWORD*)buff), //start_sector
|
||||
(*((DWORD*)buff + 1) - *((DWORD*)buff) + 1)); //sector_count
|
||||
#endif //FF_USE_TRIM
|
||||
}
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
void ff_sdmmc_set_disk_status_check(BYTE pdrv, bool enable)
|
||||
{
|
||||
s_disk_status_check_en[pdrv] = enable;
|
||||
}
|
||||
|
||||
void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card)
|
||||
{
|
||||
static const ff_diskio_impl_t sdmmc_impl = {
|
||||
.init = &ff_sdmmc_initialize,
|
||||
.status = &ff_sdmmc_status,
|
||||
.read = &ff_sdmmc_read,
|
||||
.write = &ff_sdmmc_write,
|
||||
.ioctl = &ff_sdmmc_ioctl
|
||||
};
|
||||
s_cards[pdrv] = card;
|
||||
s_disk_status_check_en[pdrv] = false;
|
||||
ff_diskio_register(pdrv, &sdmmc_impl);
|
||||
}
|
||||
|
||||
BYTE ff_diskio_get_pdrv_card(const sdmmc_card_t* card)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (card == s_cards[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enable/disable SD card status checking
|
||||
*
|
||||
* @param pdrv drive number
|
||||
* @param enable mock ff_sdmmc_status function (return 0)
|
||||
*/
|
||||
void ff_sdmmc_set_disk_status_check(BYTE pdrv, bool enable);
|
||||
|
||||
/**
|
||||
* 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(unsigned char pdrv, sdmmc_card_t* card);
|
||||
|
||||
/**
|
||||
* @brief Get the driver number corresponding to a card
|
||||
*
|
||||
* @param card The card to get its driver
|
||||
* @return Driver number to the card
|
||||
*/
|
||||
BYTE ff_diskio_get_pdrv_card(const sdmmc_card_t* card);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "diskio_impl.h"
|
||||
#include "ffconf.h"
|
||||
#include "ff.h"
|
||||
#include "esp_log.h"
|
||||
#include "diskio_wl.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "esp_compiler.h"
|
||||
|
||||
static const char* TAG = "ff_diskio_spiflash";
|
||||
|
||||
wl_handle_t ff_wl_handles[FF_VOLUMES] = {
|
||||
[0 ... FF_VOLUMES - 1] = WL_INVALID_HANDLE
|
||||
};
|
||||
|
||||
DSTATUS ff_wl_initialize (BYTE pdrv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DSTATUS ff_wl_status (BYTE pdrv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DRESULT ff_wl_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
ESP_LOGV(TAG, "ff_wl_read - pdrv=%i, sector=%i, count=%i\n", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
|
||||
wl_handle_t wl_handle = ff_wl_handles[pdrv];
|
||||
assert(wl_handle + 1);
|
||||
esp_err_t err = wl_read(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle));
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "wl_read failed (%d)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
DRESULT ff_wl_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
ESP_LOGV(TAG, "ff_wl_write - pdrv=%i, sector=%i, count=%i\n", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count);
|
||||
wl_handle_t wl_handle = ff_wl_handles[pdrv];
|
||||
assert(wl_handle + 1);
|
||||
esp_err_t err = wl_erase_range(wl_handle, sector * wl_sector_size(wl_handle), count * wl_sector_size(wl_handle));
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "wl_erase_range failed (%d)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
err = wl_write(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle));
|
||||
if (unlikely(err != ESP_OK)) {
|
||||
ESP_LOGE(TAG, "wl_write failed (%d)", err);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff)
|
||||
{
|
||||
wl_handle_t wl_handle = ff_wl_handles[pdrv];
|
||||
ESP_LOGV(TAG, "ff_wl_ioctl: cmd=%i\n", cmd);
|
||||
assert(wl_handle + 1);
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
return RES_OK;
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *) buff) = wl_size(wl_handle) / wl_sector_size(wl_handle);
|
||||
return RES_OK;
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *) buff) = wl_sector_size(wl_handle);
|
||||
return RES_OK;
|
||||
case GET_BLOCK_SIZE:
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t ff_diskio_register_wl_partition(BYTE pdrv, wl_handle_t flash_handle)
|
||||
{
|
||||
if (pdrv >= FF_VOLUMES) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
static const ff_diskio_impl_t wl_impl = {
|
||||
.init = &ff_wl_initialize,
|
||||
.status = &ff_wl_status,
|
||||
.read = &ff_wl_read,
|
||||
.write = &ff_wl_write,
|
||||
.ioctl = &ff_wl_ioctl
|
||||
};
|
||||
ff_wl_handles[pdrv] = flash_handle;
|
||||
ff_diskio_register(pdrv, &wl_impl);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
BYTE ff_diskio_get_pdrv_wl(wl_handle_t flash_handle)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (flash_handle == ff_wl_handles[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void ff_diskio_clear_pdrv_wl(wl_handle_t flash_handle)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (flash_handle == ff_wl_handles[i]) {
|
||||
ff_wl_handles[i] = WL_INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2015-2017 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.
|
||||
|
||||
#ifndef _DISKIO_WL_DEFINED
|
||||
#define _DISKIO_WL_DEFINED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "wear_levelling.h"
|
||||
|
||||
|
||||
/**
|
||||
* Register spi flash partition
|
||||
*
|
||||
* @param pdrv drive number
|
||||
* @param flash_handle handle of the wear levelling partition.
|
||||
*/
|
||||
esp_err_t ff_diskio_register_wl_partition(unsigned char pdrv, wl_handle_t flash_handle);
|
||||
unsigned char ff_diskio_get_pdrv_wl(wl_handle_t flash_handle);
|
||||
void ff_diskio_clear_pdrv_wl(wl_handle_t flash_handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _DISKIO_WL_DEFINED
|
||||
@@ -0,0 +1,168 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
from inspect import getmembers, isroutine
|
||||
from typing import Optional
|
||||
|
||||
from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct, core
|
||||
|
||||
from .exceptions import InconsistentFATAttributes, NotInitialized
|
||||
from .fatfs_state import BootSectorState
|
||||
from .utils import (ALLOWED_SECTOR_SIZES, ALLOWED_SECTORS_PER_CLUSTER, EMPTY_BYTE, FAT32, FULL_BYTE,
|
||||
SHORT_NAMES_ENCODING, FATDefaults, generate_4bytes_random, pad_string)
|
||||
|
||||
|
||||
class BootSector:
|
||||
"""
|
||||
This class describes the first sector of the volume in the Reserved Region.
|
||||
It contains data from BPB (BIOS Parameter Block) and BS (Boot sector). The fields of the BPB and BS are mixed in
|
||||
the header of the physical boot sector. Fields with prefix BPB belongs to BPB block and with prefix BS
|
||||
belongs to the actual boot sector.
|
||||
|
||||
Please beware, that the name of class BootSector refer to data both from the boot sector and BPB.
|
||||
ESP32 ignores fields with prefix "BS_"! Fields with prefix BPB_ are essential to read the filesystem.
|
||||
"""
|
||||
MAX_VOL_LAB_SIZE = 11
|
||||
MAX_OEM_NAME_SIZE = 8
|
||||
MAX_FS_TYPE_SIZE = 8
|
||||
|
||||
# the FAT specification defines 512 bytes for the boot sector header
|
||||
BOOT_HEADER_SIZE = 512
|
||||
|
||||
BOOT_SECTOR_HEADER = Struct(
|
||||
# this value reflects BS_jmpBoot used for ESP32 boot sector (any other accepted)
|
||||
'BS_jmpBoot' / Const(b'\xeb\xfe\x90'),
|
||||
'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, SHORT_NAMES_ENCODING),
|
||||
'BPB_BytsPerSec' / Int16ul,
|
||||
'BPB_SecPerClus' / Int8ul,
|
||||
'BPB_RsvdSecCnt' / Int16ul,
|
||||
'BPB_NumFATs' / Int8ul,
|
||||
'BPB_RootEntCnt' / Int16ul,
|
||||
'BPB_TotSec16' / Int16ul, # zero if the FAT type is 32, otherwise number of sectors
|
||||
'BPB_Media' / Int8ul,
|
||||
'BPB_FATSz16' / Int16ul, # for FAT32 always zero, for FAT12/FAT16 number of sectors per FAT
|
||||
'BPB_SecPerTrk' / Int16ul,
|
||||
'BPB_NumHeads' / Int16ul,
|
||||
'BPB_HiddSec' / Int32ul,
|
||||
'BPB_TotSec32' / Int32ul, # zero if the FAT type is 12/16, otherwise number of sectors
|
||||
'BS_DrvNum' / Const(b'\x80'),
|
||||
'BS_Reserved1' / Const(EMPTY_BYTE),
|
||||
'BS_BootSig' / Const(b'\x29'),
|
||||
'BS_VolID' / Int32ul,
|
||||
'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, SHORT_NAMES_ENCODING),
|
||||
'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, SHORT_NAMES_ENCODING),
|
||||
'BS_EMPTY' / Const(448 * EMPTY_BYTE),
|
||||
'Signature_word' / Const(FATDefaults.SIGNATURE_WORD)
|
||||
)
|
||||
assert BOOT_SECTOR_HEADER.sizeof() == BOOT_HEADER_SIZE
|
||||
|
||||
def __init__(self, boot_sector_state: Optional[BootSectorState] = None) -> None:
|
||||
self._parsed_header: dict = {}
|
||||
self.boot_sector_state: BootSectorState = boot_sector_state
|
||||
|
||||
def generate_boot_sector(self) -> None:
|
||||
boot_sector_state: BootSectorState = self.boot_sector_state
|
||||
if boot_sector_state is None:
|
||||
raise NotInitialized('The BootSectorState instance is not initialized!')
|
||||
volume_uuid = generate_4bytes_random()
|
||||
pad_header: bytes = (boot_sector_state.sector_size - BootSector.BOOT_HEADER_SIZE) * EMPTY_BYTE
|
||||
data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE
|
||||
root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE
|
||||
fat_tables_content: bytes = (boot_sector_state.sectors_per_fat_cnt
|
||||
* boot_sector_state.fat_tables_cnt
|
||||
* boot_sector_state.sector_size
|
||||
* EMPTY_BYTE)
|
||||
self.boot_sector_state.binary_image = (
|
||||
BootSector.BOOT_SECTOR_HEADER.build(
|
||||
dict(BS_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE),
|
||||
BPB_BytsPerSec=boot_sector_state.sector_size,
|
||||
BPB_SecPerClus=boot_sector_state.sectors_per_cluster,
|
||||
BPB_RsvdSecCnt=boot_sector_state.reserved_sectors_cnt,
|
||||
BPB_NumFATs=boot_sector_state.fat_tables_cnt,
|
||||
BPB_RootEntCnt=boot_sector_state.entries_root_count,
|
||||
# if fat type is 12 or 16 BPB_TotSec16 is filled and BPB_TotSec32 is 0x00 and vice versa
|
||||
BPB_TotSec16=0x00 if boot_sector_state.fatfs_type == FAT32 else boot_sector_state.sectors_count,
|
||||
BPB_Media=boot_sector_state.media_type,
|
||||
BPB_FATSz16=boot_sector_state.sectors_per_fat_cnt,
|
||||
BPB_SecPerTrk=boot_sector_state.sec_per_track,
|
||||
BPB_NumHeads=boot_sector_state.num_heads,
|
||||
BPB_HiddSec=boot_sector_state.hidden_sectors,
|
||||
BPB_TotSec32=boot_sector_state.sectors_count if boot_sector_state.fatfs_type == FAT32 else 0x00,
|
||||
BS_VolID=volume_uuid,
|
||||
BS_VolLab=pad_string(boot_sector_state.volume_label,
|
||||
size=BootSector.MAX_VOL_LAB_SIZE),
|
||||
BS_FilSysType=pad_string(boot_sector_state.file_sys_type,
|
||||
size=BootSector.MAX_FS_TYPE_SIZE)
|
||||
)
|
||||
) + pad_header + fat_tables_content + root_dir_content + data_content
|
||||
)
|
||||
|
||||
def parse_boot_sector(self, binary_data: bytes) -> None:
|
||||
"""
|
||||
Checks the validity of the boot sector and derives the metadata from boot sector to the structured shape.
|
||||
"""
|
||||
try:
|
||||
self._parsed_header = BootSector.BOOT_SECTOR_HEADER.parse(binary_data)
|
||||
except core.StreamError:
|
||||
raise NotInitialized('The boot sector header is not parsed successfully!')
|
||||
|
||||
if self._parsed_header['BPB_TotSec16'] != 0x00:
|
||||
sectors_count_: int = self._parsed_header['BPB_TotSec16']
|
||||
elif self._parsed_header['BPB_TotSec32'] != 0x00:
|
||||
# uncomment for FAT32 implementation
|
||||
# sectors_count_ = self._parsed_header['BPB_TotSec32']
|
||||
# possible_fat_types = [FAT32]
|
||||
assert self._parsed_header['BPB_TotSec16'] == 0
|
||||
raise NotImplementedError('FAT32 not implemented!')
|
||||
else:
|
||||
raise InconsistentFATAttributes('The number of FS sectors cannot be zero!')
|
||||
|
||||
if self._parsed_header['BPB_BytsPerSec'] not in ALLOWED_SECTOR_SIZES:
|
||||
raise InconsistentFATAttributes(f'The number of bytes '
|
||||
f"per sector is {self._parsed_header['BPB_BytsPerSec']}! "
|
||||
f'The accepted values are {ALLOWED_SECTOR_SIZES}')
|
||||
if self._parsed_header['BPB_SecPerClus'] not in ALLOWED_SECTORS_PER_CLUSTER:
|
||||
raise InconsistentFATAttributes(f'The number of sectors per cluster '
|
||||
f"is {self._parsed_header['BPB_SecPerClus']}"
|
||||
f'The accepted values are {ALLOWED_SECTORS_PER_CLUSTER}')
|
||||
|
||||
total_root_bytes: int = self._parsed_header['BPB_RootEntCnt'] * FATDefaults.ENTRY_SIZE
|
||||
root_dir_sectors_cnt_: int = total_root_bytes // self._parsed_header['BPB_BytsPerSec']
|
||||
self.boot_sector_state = BootSectorState(oem_name=self._parsed_header['BS_OEMName'],
|
||||
sector_size=self._parsed_header['BPB_BytsPerSec'],
|
||||
sectors_per_cluster=self._parsed_header['BPB_SecPerClus'],
|
||||
reserved_sectors_cnt=self._parsed_header['BPB_RsvdSecCnt'],
|
||||
fat_tables_cnt=self._parsed_header['BPB_NumFATs'],
|
||||
root_dir_sectors_cnt=root_dir_sectors_cnt_,
|
||||
sectors_count=sectors_count_,
|
||||
media_type=self._parsed_header['BPB_Media'],
|
||||
sec_per_track=self._parsed_header['BPB_SecPerTrk'],
|
||||
num_heads=self._parsed_header['BPB_NumHeads'],
|
||||
hidden_sectors=self._parsed_header['BPB_HiddSec'],
|
||||
volume_label=self._parsed_header['BS_VolLab'],
|
||||
file_sys_type=self._parsed_header['BS_FilSysType'],
|
||||
volume_uuid=self._parsed_header['BS_VolID'])
|
||||
self.boot_sector_state.binary_image = binary_data
|
||||
assert self.boot_sector_state.file_sys_type in (f'FAT{self.boot_sector_state.fatfs_type} ', 'FAT ')
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
FATFS properties parser (internal helper tool for fatfsgen.py/fatfsparse.py)
|
||||
Provides all the properties of given FATFS instance by parsing its boot sector (returns formatted string)
|
||||
"""
|
||||
|
||||
if self._parsed_header == {}:
|
||||
return 'Boot sector is not initialized!'
|
||||
res: str = 'FATFS properties:\n'
|
||||
for member in getmembers(self.boot_sector_state, lambda a: not (isroutine(a))):
|
||||
prop_ = getattr(self.boot_sector_state, member[0])
|
||||
if isinstance(prop_, int) or isinstance(prop_, str) and not member[0].startswith('_'):
|
||||
res += f'{member[0]}: {prop_}\n'
|
||||
return res
|
||||
|
||||
@property
|
||||
def binary_image(self) -> bytes:
|
||||
# when BootSector is not instantiated, self.boot_sector_state might be None
|
||||
if self.boot_sector_state is None or len(self.boot_sector_state.binary_image) == 0:
|
||||
raise NotInitialized('Boot sector is not initialized!')
|
||||
bin_image_: bytes = self.boot_sector_state.binary_image
|
||||
return bin_image_
|
||||
@@ -0,0 +1,213 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Dict, Optional
|
||||
|
||||
from construct import Int16ul
|
||||
|
||||
from .fatfs_state import BootSectorState
|
||||
from .utils import (EMPTY_BYTE, FAT12, FAT16, build_byte, merge_by_half_byte_12_bit_little_endian,
|
||||
split_by_half_byte_12_bit_little_endian)
|
||||
|
||||
|
||||
def get_dir_size(is_root: bool, boot_sector: BootSectorState) -> int:
|
||||
dir_size_: int = boot_sector.root_dir_sectors_cnt * boot_sector.sector_size if is_root else boot_sector.sector_size
|
||||
return dir_size_
|
||||
|
||||
|
||||
class Cluster:
|
||||
"""
|
||||
class Cluster handles values in FAT table and allocates sectors in data region.
|
||||
"""
|
||||
RESERVED_BLOCK_ID: int = 0
|
||||
ROOT_BLOCK_ID: int = 1
|
||||
ALLOCATED_BLOCK_FAT12: int = 0xFFF
|
||||
ALLOCATED_BLOCK_FAT16: int = 0xFFFF
|
||||
ALLOCATED_BLOCK_SWITCH = {FAT12: ALLOCATED_BLOCK_FAT12, FAT16: ALLOCATED_BLOCK_FAT16}
|
||||
INITIAL_BLOCK_SWITCH: Dict[int, int] = {FAT12: 0xFF8, FAT16: 0xFFF8}
|
||||
|
||||
def __init__(self,
|
||||
cluster_id: int,
|
||||
boot_sector_state: BootSectorState,
|
||||
init_: bool) -> None:
|
||||
"""
|
||||
Initially, if init_ is False, the cluster is virtual and is not allocated (doesn't do changes in the FAT).
|
||||
:param cluster_id: the cluster ID - a key value linking the file's cluster,
|
||||
the corresponding physical cluster (data region) and the FAT table cluster.
|
||||
:param boot_sector_state: auxiliary structure holding the file-system's metadata
|
||||
:param init_: True for allocation the cluster on instantiation, otherwise False.
|
||||
:returns: None
|
||||
"""
|
||||
self.id: int = cluster_id
|
||||
self.boot_sector_state: BootSectorState = boot_sector_state
|
||||
|
||||
self._next_cluster = None # type: Optional[Cluster]
|
||||
# First cluster in FAT is reserved, low 8 bits contains BPB_Media and the rest is filled with 1
|
||||
# e.g. the esp32 media type is 0xF8 thus the FAT[0] = 0xFF8 for FAT12, 0xFFF8 for FAT16
|
||||
if self.id == Cluster.RESERVED_BLOCK_ID and init_:
|
||||
self.set_in_fat(self.INITIAL_BLOCK_SWITCH[self.boot_sector_state.fatfs_type])
|
||||
return
|
||||
self.cluster_data_address: int = self._compute_cluster_data_address()
|
||||
assert self.cluster_data_address
|
||||
|
||||
@property
|
||||
def next_cluster(self): # type: () -> Optional[Cluster]
|
||||
return self._next_cluster
|
||||
|
||||
@next_cluster.setter
|
||||
def next_cluster(self, value): # type: (Optional[Cluster]) -> None
|
||||
self._next_cluster = value
|
||||
|
||||
def _cluster_id_to_fat_position_in_bits(self, _id: int) -> int:
|
||||
"""
|
||||
This private method calculates the position of the memory block (cluster) in the FAT table.
|
||||
|
||||
:param _id: the cluster ID - a key value linking the file's cluster,
|
||||
the corresponding physical cluster (data region) and the FAT table cluster.
|
||||
:returns: bit offset of the cluster in FAT
|
||||
e.g.:
|
||||
00003000: 42 65 00 2E 00 74 00 78 00 74 00 0F 00 43 FF FF
|
||||
|
||||
For FAT12 the third cluster has value = 0x02E and ID = 2.
|
||||
Its bit-address is 24 (24 bits preceding, 0-indexed), because 0x2E starts at the bit-offset 24.
|
||||
"""
|
||||
logical_position_: int = self.boot_sector_state.fatfs_type * _id
|
||||
return logical_position_
|
||||
|
||||
@staticmethod
|
||||
def compute_cluster_data_address(boot_sector_state: BootSectorState, id_: int) -> int:
|
||||
"""
|
||||
This method translates the id of the cluster to the address in data region.
|
||||
|
||||
:param boot_sector_state: the class with FS shared data
|
||||
:param id_: id of the cluster
|
||||
:returns: integer denoting the address of the cluster in the data region
|
||||
"""
|
||||
data_address_: int = boot_sector_state.root_directory_start
|
||||
if not id_ == Cluster.ROOT_BLOCK_ID:
|
||||
# the first data cluster id is 2 (we have to subtract reserved cluster and cluster for root)
|
||||
data_address_ = boot_sector_state.sector_size * (id_ - 2) + boot_sector_state.data_region_start
|
||||
return data_address_
|
||||
|
||||
def _compute_cluster_data_address(self) -> int:
|
||||
return self.compute_cluster_data_address(self.boot_sector_state, self.id)
|
||||
|
||||
@property
|
||||
def fat_cluster_address(self) -> int:
|
||||
"""Determines how many bits precede the first bit of the cluster in FAT"""
|
||||
return self._cluster_id_to_fat_position_in_bits(self.id)
|
||||
|
||||
@property
|
||||
def real_cluster_address(self) -> int:
|
||||
"""
|
||||
The property method computes the real address of the cluster in the FAT region. Result is simply
|
||||
address of the cluster in fat + fat table address.
|
||||
"""
|
||||
cluster_address: int = self.boot_sector_state.fat_table_start_address + self.fat_cluster_address // 8
|
||||
return cluster_address
|
||||
|
||||
def get_from_fat(self) -> int:
|
||||
"""
|
||||
Calculating the value in the FAT block, that denotes if the block is full, empty, or chained to other block.
|
||||
|
||||
For FAT12 is the block stored in one and half byte. If the order of the block is even the first byte and second
|
||||
half of the second byte belongs to the block. First half of the second byte and the third byte belongs to
|
||||
the second block.
|
||||
|
||||
e.g. b'\xff\x0f\x00' stores two blocks. First of them is evenly ordered (index 0) and is set to 0xfff,
|
||||
that means full block that is final in chain of blocks
|
||||
and second block is set to 0x000 that means empty block.
|
||||
|
||||
three bytes - AB XC YZ - stores two blocks - CAB YZX
|
||||
"""
|
||||
address_: int = self.real_cluster_address
|
||||
bin_img_: bytearray = self.boot_sector_state.binary_image
|
||||
if self.boot_sector_state.fatfs_type == FAT12:
|
||||
if self.fat_cluster_address % 8 == 0:
|
||||
# even block
|
||||
return bin_img_[self.real_cluster_address] | ((bin_img_[self.real_cluster_address + 1] & 0x0F) << 8)
|
||||
# odd block
|
||||
return ((bin_img_[self.real_cluster_address] & 0xF0) >> 4) | (bin_img_[self.real_cluster_address + 1] << 4)
|
||||
if self.boot_sector_state.fatfs_type == FAT16:
|
||||
return int.from_bytes(bin_img_[address_:address_ + 2], byteorder='little')
|
||||
raise NotImplementedError('Only valid fatfs types are FAT12 and FAT16.')
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
"""
|
||||
The property method takes a look into the binary array and checks if the bytes ordered by little endian
|
||||
and relates to the current cluster are all zeros (which denotes they are empty).
|
||||
"""
|
||||
return self.get_from_fat() == 0x00
|
||||
|
||||
def set_in_fat(self, value: int) -> None:
|
||||
"""
|
||||
Sets cluster in FAT to certain value.
|
||||
Firstly, we split the target value into 3 half bytes (max value is 0xfff).
|
||||
Then we could encounter two situations:
|
||||
1. if the cluster index (indexed from zero) is even, we set the full byte computed by
|
||||
self.cluster_id_to_logical_position_in_bits and the second half of the consequent byte.
|
||||
Order of half bytes is 2, 1, 3.
|
||||
|
||||
2. if the cluster index is odd, we set the first half of the computed byte and the full consequent byte.
|
||||
Order of half bytes is 1, 3, 2.
|
||||
"""
|
||||
|
||||
def _set_msb_half_byte(address: int, value_: int) -> None:
|
||||
"""
|
||||
Sets 4 most significant bits (msb half-byte) of 'boot_sector_state.binary_image' at given
|
||||
'address' to 'value_' (size of variable 'value_' is half byte)
|
||||
|
||||
If a byte contents is 0b11110000, the msb half-byte would be 0b1111
|
||||
"""
|
||||
self.boot_sector_state.binary_image[address] &= 0x0f
|
||||
self.boot_sector_state.binary_image[address] |= value_ << 4
|
||||
|
||||
def _set_lsb_half_byte(address: int, value_: int) -> None:
|
||||
"""
|
||||
Sets 4 least significant bits (lsb half-byte) of 'boot_sector_state.binary_image' at given
|
||||
'address' to 'value_' (size of variable 'value_' is half byte)
|
||||
|
||||
If a byte contents is 0b11110000, the lsb half-byte would be 0b0000
|
||||
"""
|
||||
self.boot_sector_state.binary_image[address] &= 0xf0
|
||||
self.boot_sector_state.binary_image[address] |= value_
|
||||
|
||||
# value must fit into number of bits of the fat (12, 16 or 32)
|
||||
assert value <= (1 << self.boot_sector_state.fatfs_type) - 1
|
||||
half_bytes = split_by_half_byte_12_bit_little_endian(value)
|
||||
bin_img_: bytearray = self.boot_sector_state.binary_image
|
||||
|
||||
if self.boot_sector_state.fatfs_type == FAT12:
|
||||
assert merge_by_half_byte_12_bit_little_endian(*half_bytes) == value
|
||||
if self.fat_cluster_address % 8 == 0:
|
||||
# even block
|
||||
bin_img_[self.real_cluster_address] = build_byte(half_bytes[1], half_bytes[0])
|
||||
_set_lsb_half_byte(self.real_cluster_address + 1, half_bytes[2])
|
||||
elif self.fat_cluster_address % 8 != 0:
|
||||
# odd block
|
||||
_set_msb_half_byte(self.real_cluster_address, half_bytes[0])
|
||||
bin_img_[self.real_cluster_address + 1] = build_byte(half_bytes[2], half_bytes[1])
|
||||
elif self.boot_sector_state.fatfs_type == FAT16:
|
||||
bin_img_[self.real_cluster_address:self.real_cluster_address + 2] = Int16ul.build(value)
|
||||
assert self.get_from_fat() == value
|
||||
|
||||
@property
|
||||
def is_root(self) -> bool:
|
||||
"""
|
||||
The FAT12/FAT16 contains only one root directory,
|
||||
the root directory allocates the first cluster with the ID `ROOT_BLOCK_ID`.
|
||||
The method checks if the cluster belongs to the root directory.
|
||||
"""
|
||||
return self.id == Cluster.ROOT_BLOCK_ID
|
||||
|
||||
def allocate_cluster(self) -> None:
|
||||
"""
|
||||
This method sets bits in FAT table to `allocated` and clean the corresponding sector(s)
|
||||
"""
|
||||
self.set_in_fat(self.ALLOCATED_BLOCK_SWITCH[self.boot_sector_state.fatfs_type])
|
||||
|
||||
cluster_start = self.cluster_data_address
|
||||
dir_size = get_dir_size(self.is_root, self.boot_sector_state)
|
||||
cluster_end = cluster_start + dir_size
|
||||
self.boot_sector_state.binary_image[cluster_start:cluster_end] = dir_size * EMPTY_BYTE
|
||||
@@ -0,0 +1,253 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct
|
||||
|
||||
from .exceptions import LowerCaseException, TooLongNameException
|
||||
from .fatfs_state import FATFSState
|
||||
from .utils import (DATETIME, EMPTY_BYTE, FATFS_INCEPTION, MAX_EXT_SIZE, MAX_NAME_SIZE, SHORT_NAMES_ENCODING,
|
||||
FATDefaults, build_date_entry, build_time_entry, is_valid_fatfs_name, pad_string)
|
||||
|
||||
|
||||
class Entry:
|
||||
"""
|
||||
The class Entry represents entry of the directory.
|
||||
"""
|
||||
ATTR_READ_ONLY: int = 0x01
|
||||
ATTR_HIDDEN: int = 0x02
|
||||
ATTR_SYSTEM: int = 0x04
|
||||
ATTR_VOLUME_ID: int = 0x08
|
||||
ATTR_DIRECTORY: int = 0x10 # directory
|
||||
ATTR_ARCHIVE: int = 0x20 # file
|
||||
ATTR_LONG_NAME: int = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID
|
||||
|
||||
# indexes in the entry structure and sizes in bytes, not in characters (encoded using 2 bytes for lfn)
|
||||
LDIR_Name1_IDX: int = 1
|
||||
LDIR_Name1_SIZE: int = 5
|
||||
LDIR_Name2_IDX: int = 14
|
||||
LDIR_Name2_SIZE: int = 6
|
||||
LDIR_Name3_IDX: int = 28
|
||||
LDIR_Name3_SIZE: int = 2
|
||||
|
||||
# short entry in long file names
|
||||
LDIR_DIR_NTRES: int = 0x18
|
||||
# one entry can hold 13 characters with size 2 bytes distributed in three regions of the 32 bytes entry
|
||||
CHARS_PER_ENTRY: int = LDIR_Name1_SIZE + LDIR_Name2_SIZE + LDIR_Name3_SIZE
|
||||
|
||||
# the last 16 bytes record in the LFN entry has first byte masked with the following value
|
||||
LAST_RECORD_LFN_ENTRY: int = 0x40
|
||||
SHORT_ENTRY: int = -1
|
||||
# this value is used for short-like entry but with accepted lower case
|
||||
SHORT_ENTRY_LN: int = 0
|
||||
|
||||
# The 1st January 1980 00:00:00
|
||||
DEFAULT_DATE: DATETIME = (FATFS_INCEPTION.year, FATFS_INCEPTION.month, FATFS_INCEPTION.day)
|
||||
DEFAULT_TIME: DATETIME = (FATFS_INCEPTION.hour, FATFS_INCEPTION.minute, FATFS_INCEPTION.second)
|
||||
|
||||
ENTRY_FORMAT_SHORT_NAME = Struct(
|
||||
'DIR_Name' / PaddedString(MAX_NAME_SIZE, SHORT_NAMES_ENCODING),
|
||||
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, SHORT_NAMES_ENCODING),
|
||||
'DIR_Attr' / Int8ul,
|
||||
'DIR_NTRes' / Int8ul, # this tagged for lfn (0x00 for lfn prefix, 0x18 for short name in lfn)
|
||||
'DIR_CrtTimeTenth' / Const(EMPTY_BYTE), # ignored by esp-idf fatfs library
|
||||
'DIR_CrtTime' / Int16ul, # ignored by esp-idf fatfs library
|
||||
'DIR_CrtDate' / Int16ul, # ignored by esp-idf fatfs library
|
||||
'DIR_LstAccDate' / Int16ul, # must be same as DIR_WrtDate
|
||||
'DIR_FstClusHI' / Const(2 * EMPTY_BYTE),
|
||||
'DIR_WrtTime' / Int16ul,
|
||||
'DIR_WrtDate' / Int16ul,
|
||||
'DIR_FstClusLO' / Int16ul,
|
||||
'DIR_FileSize' / Int32ul,
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
entry_id: int,
|
||||
parent_dir_entries_address: int,
|
||||
fatfs_state: FATFSState) -> None:
|
||||
self.fatfs_state: FATFSState = fatfs_state
|
||||
self.id: int = entry_id
|
||||
self.entry_address: int = parent_dir_entries_address + self.id * FATDefaults.ENTRY_SIZE
|
||||
self._is_alias: bool = False
|
||||
self._is_empty: bool = True
|
||||
|
||||
@staticmethod
|
||||
def get_cluster_id(obj_: dict) -> int:
|
||||
cluster_id_: int = obj_['DIR_FstClusLO']
|
||||
return cluster_id_
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
return self._is_empty
|
||||
|
||||
@staticmethod
|
||||
def _parse_entry(entry_bytearray: Union[bytearray, bytes]) -> dict:
|
||||
entry_: dict = Entry.ENTRY_FORMAT_SHORT_NAME.parse(entry_bytearray)
|
||||
return entry_
|
||||
|
||||
@staticmethod
|
||||
def _build_entry(**kwargs) -> bytes: # type: ignore
|
||||
entry_: bytes = Entry.ENTRY_FORMAT_SHORT_NAME.build(dict(**kwargs))
|
||||
return entry_
|
||||
|
||||
@staticmethod
|
||||
def _build_entry_long(names: List[bytes], checksum: int, order: int, is_last: bool) -> bytes:
|
||||
"""
|
||||
Long entry starts with 1 bytes of the order, if the entry is the last in the chain it is or-masked with 0x40,
|
||||
otherwise is without change (or masked with 0x00). The following example shows 3 entries:
|
||||
first two (0x2000-0x2040) are long in the reverse order and the last one (0x2040-0x2060) is short.
|
||||
The entries define file name "thisisverylongfilenama.txt".
|
||||
|
||||
00002000: 42 67 00 66 00 69 00 6C 00 65 00 0F 00 43 6E 00 Bg.f.i.l.e...Cn.
|
||||
00002010: 61 00 6D 00 61 00 2E 00 74 00 00 00 78 00 74 00 a.m.a...t...x.t.
|
||||
00002020: 01 74 00 68 00 69 00 73 00 69 00 0F 00 43 73 00 .t.h.i.s.i...Cs.
|
||||
00002030: 76 00 65 00 72 00 79 00 6C 00 00 00 6F 00 6E 00 v.e.r.y.l...o.n.
|
||||
00002040: 54 48 49 53 49 53 7E 31 54 58 54 20 00 00 00 00 THISIS~1TXT.....
|
||||
00002050: 21 00 00 00 00 00 00 00 21 00 02 00 15 00 00 00 !.......!.......
|
||||
"""
|
||||
order |= (Entry.LAST_RECORD_LFN_ENTRY if is_last else 0x00)
|
||||
long_entry: bytes = (Int8ul.build(order) + # order of the long name entry (possibly masked with 0x40)
|
||||
names[0] + # first 5 characters (10 bytes) of the name part
|
||||
Int8ul.build(Entry.ATTR_LONG_NAME) + # one byte entity type ATTR_LONG_NAME
|
||||
Int8ul.build(0) + # one byte of zeros
|
||||
Int8ul.build(checksum) + # lfn_checksum defined in utils.py
|
||||
names[1] + # next 6 characters (12 bytes) of the name part
|
||||
Int16ul.build(0) + # 2 bytes of zeros
|
||||
names[2]) # last 2 characters (4 bytes) of the name part
|
||||
return long_entry
|
||||
|
||||
@staticmethod
|
||||
def parse_entry_long(entry_bytes_: bytes, my_check: int) -> dict:
|
||||
order_ = Int8ul.parse(entry_bytes_[0:1])
|
||||
names0 = entry_bytes_[1:11]
|
||||
if Int8ul.parse(entry_bytes_[12:13]) != 0 or Int16ul.parse(entry_bytes_[26:28]) != 0 or Int8ul.parse(entry_bytes_[11:12]) != 15:
|
||||
return {}
|
||||
if Int8ul.parse(entry_bytes_[13:14]) != my_check:
|
||||
return {}
|
||||
names1 = entry_bytes_[14:26]
|
||||
names2 = entry_bytes_[28:32]
|
||||
return {
|
||||
'order': order_,
|
||||
'name1': names0,
|
||||
'name2': names1,
|
||||
'name3': names2,
|
||||
'is_last': bool(order_ & Entry.LAST_RECORD_LFN_ENTRY == Entry.LAST_RECORD_LFN_ENTRY)
|
||||
}
|
||||
|
||||
@property
|
||||
def entry_bytes(self) -> bytes:
|
||||
"""
|
||||
:returns: Bytes defining the entry belonging to the given instance.
|
||||
"""
|
||||
start_: int = self.entry_address
|
||||
entry_: bytes = self.fatfs_state.binary_image[start_: start_ + FATDefaults.ENTRY_SIZE]
|
||||
return entry_
|
||||
|
||||
@entry_bytes.setter
|
||||
def entry_bytes(self, value: bytes) -> None:
|
||||
"""
|
||||
:param value: new content of the entry
|
||||
:returns: None
|
||||
|
||||
The setter sets the content of the entry in bytes.
|
||||
"""
|
||||
self.fatfs_state.binary_image[self.entry_address: self.entry_address + FATDefaults.ENTRY_SIZE] = value
|
||||
|
||||
def _clean_entry(self) -> None:
|
||||
self.entry_bytes: bytes = FATDefaults.ENTRY_SIZE * EMPTY_BYTE
|
||||
|
||||
def allocate_entry(self,
|
||||
first_cluster_id: int,
|
||||
entity_name: str,
|
||||
entity_type: int,
|
||||
entity_extension: str = '',
|
||||
size: int = 0,
|
||||
date: DATETIME = DEFAULT_DATE,
|
||||
time: DATETIME = DEFAULT_TIME,
|
||||
lfn_order: int = SHORT_ENTRY,
|
||||
lfn_names: Optional[List[bytes]] = None,
|
||||
lfn_checksum_: int = 0,
|
||||
fits_short: bool = False,
|
||||
lfn_is_last: bool = False) -> None:
|
||||
"""
|
||||
:param first_cluster_id: id of the first data cluster for given entry
|
||||
:param entity_name: name recorded in the entry
|
||||
:param entity_extension: extension recorded in the entry
|
||||
:param size: size of the content of the file
|
||||
:param date: denotes year (actual year minus 1980), month number day of the month (minimal valid is (0, 1, 1))
|
||||
:param time: denotes hour, minute and second with granularity 2 seconds (sec // 2)
|
||||
:param entity_type: type of the entity (file [0x20] or directory [0x10])
|
||||
:param lfn_order: if long names support is enabled, defines order in long names entries sequence (-1 for short)
|
||||
:param lfn_names: if the entry is dedicated for long names the lfn_names contains
|
||||
LDIR_Name1, LDIR_Name2 and LDIR_Name3 in this order
|
||||
:param lfn_checksum_: use only for long file names, checksum calculated lfn_checksum function
|
||||
:param fits_short: determines if the name fits in 8.3 filename
|
||||
:param lfn_is_last: determines if the long file name entry is holds last part of the name,
|
||||
thus its address is first in the physical order
|
||||
:returns: None
|
||||
|
||||
:raises LowerCaseException: In case when long_names_enabled is set to False and filename exceeds 8 chars
|
||||
for name or 3 chars for extension the exception is raised
|
||||
:raises TooLongNameException: When long_names_enabled is set to False and name doesn't fit to 8.3 filename
|
||||
an exception is raised
|
||||
"""
|
||||
valid_full_name: bool = is_valid_fatfs_name(entity_name) and is_valid_fatfs_name(entity_extension)
|
||||
if not (valid_full_name or lfn_order >= 0):
|
||||
raise LowerCaseException('Lower case is not supported in short name entry, use upper case.')
|
||||
|
||||
if self.fatfs_state.use_default_datetime:
|
||||
date = self.DEFAULT_DATE
|
||||
time = self.DEFAULT_TIME
|
||||
|
||||
# clean entry before allocation
|
||||
self._clean_entry()
|
||||
self._is_empty = False
|
||||
|
||||
object_name = entity_name.upper() if not self.fatfs_state.long_names_enabled else entity_name
|
||||
object_extension = entity_extension.upper() if not self.fatfs_state.long_names_enabled else entity_extension
|
||||
|
||||
exceeds_short_name: bool = len(object_name) > MAX_NAME_SIZE or len(object_extension) > MAX_EXT_SIZE
|
||||
if not self.fatfs_state.long_names_enabled and exceeds_short_name:
|
||||
raise TooLongNameException(
|
||||
'Maximal length of the object name is {} characters and {} characters for extension!'.format(
|
||||
MAX_NAME_SIZE, MAX_EXT_SIZE
|
||||
)
|
||||
)
|
||||
|
||||
start_address = self.entry_address
|
||||
end_address = start_address + FATDefaults.ENTRY_SIZE
|
||||
if lfn_order in (self.SHORT_ENTRY, self.SHORT_ENTRY_LN):
|
||||
date_entry_: int = build_date_entry(*date)
|
||||
time_entry: int = build_time_entry(*time)
|
||||
self.fatfs_state.binary_image[start_address: end_address] = self._build_entry(
|
||||
DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE),
|
||||
DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE),
|
||||
DIR_Attr=entity_type,
|
||||
DIR_NTRes=0x00 if (not self.fatfs_state.long_names_enabled) or (not fits_short) else 0x18,
|
||||
DIR_FstClusLO=first_cluster_id,
|
||||
DIR_FileSize=size,
|
||||
DIR_CrtDate=date_entry_, # ignored by esp-idf fatfs library
|
||||
DIR_LstAccDate=date_entry_, # must be same as DIR_WrtDate
|
||||
DIR_WrtDate=date_entry_,
|
||||
DIR_CrtTime=time_entry, # ignored by esp-idf fatfs library
|
||||
DIR_WrtTime=time_entry
|
||||
)
|
||||
else:
|
||||
assert lfn_names is not None
|
||||
self.fatfs_state.binary_image[start_address: end_address] = self._build_entry_long(lfn_names,
|
||||
lfn_checksum_,
|
||||
lfn_order,
|
||||
lfn_is_last)
|
||||
|
||||
def update_content_size(self, content_size: int) -> None:
|
||||
"""
|
||||
:param content_size: the new size of the file content in bytes
|
||||
:returns: None
|
||||
|
||||
This method parses the binary entry to the construct structure, updates the content size of the file
|
||||
and builds new binary entry.
|
||||
"""
|
||||
parsed_entry = self._parse_entry(self.entry_bytes)
|
||||
parsed_entry.DIR_FileSize = content_size # type: ignore
|
||||
self.entry_bytes = Entry.ENTRY_FORMAT_SHORT_NAME.build(parsed_entry)
|
||||
@@ -0,0 +1,54 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class WriteDirectoryException(Exception):
|
||||
"""
|
||||
Exception is raised when the user tries to write the content into the directory instead of file
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NoFreeClusterException(Exception):
|
||||
"""
|
||||
Exception is raised when the user tries allocate cluster but no free one is available
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class LowerCaseException(Exception):
|
||||
"""
|
||||
Exception is raised when the user tries to write file or directory with lower case
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TooLongNameException(Exception):
|
||||
"""
|
||||
Exception is raised when long name support is not enabled and user tries to write file longer then allowed
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NotInitialized(Exception):
|
||||
"""
|
||||
Exception is raised when the user tries to access not initialized property
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class WLNotInitialized(Exception):
|
||||
"""
|
||||
Exception is raised when the user tries to write fatfs not initialized with wear levelling
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class FatalError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InconsistentFATAttributes(Exception):
|
||||
"""
|
||||
Caused by e.g. wrong number of clusters for given FAT type
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,100 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from .cluster import Cluster
|
||||
from .exceptions import NoFreeClusterException
|
||||
from .fatfs_state import BootSectorState
|
||||
|
||||
|
||||
class FAT:
|
||||
"""
|
||||
The FAT represents the FAT region in file system. It is responsible for storing clusters
|
||||
and chaining them in case we need to extend file or directory to more clusters.
|
||||
"""
|
||||
|
||||
def allocate_root_dir(self) -> None:
|
||||
"""
|
||||
The root directory is implicitly created with the FatFS,
|
||||
its block is on the index 1 (second index) and is allocated implicitly.
|
||||
"""
|
||||
self.clusters[Cluster.ROOT_BLOCK_ID].allocate_cluster()
|
||||
|
||||
def __init__(self, boot_sector_state: BootSectorState, init_: bool) -> None:
|
||||
self._first_free_cluster_id = 1
|
||||
self.boot_sector_state = boot_sector_state
|
||||
self.clusters: List[Cluster] = [Cluster(cluster_id=i,
|
||||
boot_sector_state=self.boot_sector_state,
|
||||
init_=init_) for i in range(self.boot_sector_state.clusters)]
|
||||
if init_:
|
||||
self.allocate_root_dir()
|
||||
|
||||
def get_cluster_value(self, cluster_id_: int) -> int:
|
||||
"""
|
||||
The method retrieves the values of the FAT memory block.
|
||||
E.g. in case of FAT12:
|
||||
00000000: F8 FF FF 55 05 00 00 00 00 00 00 00 00 00 00 00
|
||||
|
||||
The reserved value is 0xFF8, the value of first cluster if 0xFFF, thus is last in chain,
|
||||
and the value of the second cluster is 0x555, so refers to the cluster number 0x555.
|
||||
"""
|
||||
fat_cluster_value_: int = self.clusters[cluster_id_].get_from_fat()
|
||||
return fat_cluster_value_
|
||||
|
||||
def is_cluster_last(self, cluster_id_: int) -> bool:
|
||||
"""
|
||||
Checks if the cluster is last in its cluster chain. If the value of the cluster is
|
||||
0xFFF for FAT12, 0xFFFF for FAT16 or 0xFFFFFFFF for FAT32, the cluster is the last.
|
||||
"""
|
||||
value_ = self.get_cluster_value(cluster_id_)
|
||||
is_cluster_last_: bool = value_ == (1 << self.boot_sector_state.fatfs_type) - 1
|
||||
return is_cluster_last_
|
||||
|
||||
def get_chained_content(self, cluster_id_: int, size: Optional[int] = None) -> bytearray:
|
||||
"""
|
||||
The purpose of the method is retrieving the content from chain of clusters when the FAT FS partition
|
||||
is analyzed. The file entry provides the reference to the first cluster, this method
|
||||
traverses linked list of clusters and append partial results to the content.
|
||||
"""
|
||||
binary_image: bytearray = self.boot_sector_state.binary_image
|
||||
|
||||
data_address_ = Cluster.compute_cluster_data_address(self.boot_sector_state, cluster_id_)
|
||||
content_ = binary_image[data_address_: data_address_ + self.boot_sector_state.sector_size]
|
||||
|
||||
while not self.is_cluster_last(cluster_id_):
|
||||
cluster_id_ = self.get_cluster_value(cluster_id_)
|
||||
data_address_ = Cluster.compute_cluster_data_address(self.boot_sector_state, cluster_id_)
|
||||
content_ += binary_image[data_address_: data_address_ + self.boot_sector_state.sector_size]
|
||||
# the size is None if the object is directory
|
||||
if size is None:
|
||||
return content_
|
||||
return content_[:size]
|
||||
|
||||
def find_free_cluster(self) -> Cluster:
|
||||
"""
|
||||
Returns the first free cluster and increments value of `self._first_free_cluster_id`.
|
||||
The method works only in context of creating a partition from scratch.
|
||||
In situations where the clusters are allocated and freed during the run of the program,
|
||||
might the method cause `Out of space` error despite there would be free clusters.
|
||||
"""
|
||||
|
||||
if self._first_free_cluster_id + 1 >= len(self.clusters):
|
||||
raise NoFreeClusterException('No free cluster available!')
|
||||
cluster = self.clusters[self._first_free_cluster_id + 1]
|
||||
if not cluster.is_empty:
|
||||
raise NoFreeClusterException('No free cluster available!')
|
||||
cluster.allocate_cluster()
|
||||
self._first_free_cluster_id += 1
|
||||
return cluster
|
||||
|
||||
def allocate_chain(self, first_cluster: Cluster, size: int) -> None:
|
||||
"""
|
||||
Allocates the linked list of clusters needed for the given file or directory.
|
||||
"""
|
||||
current = first_cluster
|
||||
for _ in range(size - 1):
|
||||
free_cluster = self.find_free_cluster()
|
||||
current.next_cluster = free_cluster
|
||||
current.set_in_fat(free_cluster.id)
|
||||
current = free_cluster
|
||||
@@ -0,0 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from .boot_sector import BootSector
|
||||
from .utils import read_filesystem
|
||||
|
||||
|
||||
class FATFSParser:
|
||||
|
||||
def __init__(self, image_file_path: str, wl_support: bool = False) -> None:
|
||||
if wl_support:
|
||||
raise NotImplementedError('Parser is not implemented for WL yet.')
|
||||
self.fatfs = read_filesystem(image_file_path)
|
||||
|
||||
# when wl is not supported we expect boot sector to be the first
|
||||
self.parsed_header = BootSector.BOOT_SECTOR_HEADER.parse(self.fatfs[:BootSector.BOOT_HEADER_SIZE])
|
||||
print(BootSector)
|
||||
@@ -0,0 +1,170 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from textwrap import dedent
|
||||
from typing import Optional
|
||||
|
||||
from .exceptions import InconsistentFATAttributes
|
||||
from .utils import (ALLOWED_SECTOR_SIZES, FAT12, FAT12_MAX_CLUSTERS, FAT16, FAT16_MAX_CLUSTERS,
|
||||
RESERVED_CLUSTERS_COUNT, FATDefaults, get_fat_sectors_count, get_fatfs_type,
|
||||
get_non_data_sectors_cnt, number_of_clusters)
|
||||
|
||||
|
||||
class FATFSState:
|
||||
"""
|
||||
The class represents the state and the configuration of the FATFS.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
sector_size: int,
|
||||
reserved_sectors_cnt: int,
|
||||
root_dir_sectors_cnt: int,
|
||||
size: int,
|
||||
media_type: int,
|
||||
sectors_per_cluster: int,
|
||||
volume_label: str,
|
||||
oem_name: str,
|
||||
fat_tables_cnt: int,
|
||||
sec_per_track: int,
|
||||
num_heads: int,
|
||||
hidden_sectors: int,
|
||||
file_sys_type: str,
|
||||
use_default_datetime: bool,
|
||||
explicit_fat_type: Optional[int] = None,
|
||||
long_names_enabled: bool = False):
|
||||
self.boot_sector_state = BootSectorState(oem_name=oem_name,
|
||||
sector_size=sector_size,
|
||||
sectors_per_cluster=sectors_per_cluster,
|
||||
reserved_sectors_cnt=reserved_sectors_cnt,
|
||||
fat_tables_cnt=fat_tables_cnt,
|
||||
root_dir_sectors_cnt=root_dir_sectors_cnt,
|
||||
sectors_count=size // sector_size,
|
||||
media_type=media_type,
|
||||
sec_per_track=sec_per_track,
|
||||
num_heads=num_heads,
|
||||
hidden_sectors=hidden_sectors,
|
||||
volume_label=volume_label,
|
||||
file_sys_type=file_sys_type,
|
||||
volume_uuid=-1)
|
||||
|
||||
self._explicit_fat_type: Optional[int] = explicit_fat_type
|
||||
self.long_names_enabled: bool = long_names_enabled
|
||||
self.use_default_datetime: bool = use_default_datetime
|
||||
|
||||
if (size // sector_size) * sectors_per_cluster in (FAT12_MAX_CLUSTERS, FAT16_MAX_CLUSTERS):
|
||||
print('WARNING: It is not recommended to create FATFS with bounding '
|
||||
f'count of clusters: {FAT12_MAX_CLUSTERS} or {FAT16_MAX_CLUSTERS}')
|
||||
self.check_fat_type()
|
||||
|
||||
@property
|
||||
def binary_image(self) -> bytearray:
|
||||
return self.boot_sector_state.binary_image
|
||||
|
||||
@binary_image.setter
|
||||
def binary_image(self, value: bytearray) -> None:
|
||||
self.boot_sector_state.binary_image = value
|
||||
|
||||
def check_fat_type(self) -> None:
|
||||
_type = self.boot_sector_state.fatfs_type
|
||||
if self._explicit_fat_type is not None and self._explicit_fat_type != _type:
|
||||
raise InconsistentFATAttributes(dedent(
|
||||
f"""FAT type you specified is inconsistent with other attributes of the system.
|
||||
The specified FATFS type: FAT{self._explicit_fat_type}
|
||||
The actual FATFS type: FAT{_type}"""))
|
||||
if _type not in (FAT12, FAT16):
|
||||
raise NotImplementedError('FAT32 is currently not supported.')
|
||||
|
||||
|
||||
class BootSectorState:
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self,
|
||||
oem_name: str,
|
||||
sector_size: int,
|
||||
sectors_per_cluster: int,
|
||||
reserved_sectors_cnt: int,
|
||||
fat_tables_cnt: int,
|
||||
root_dir_sectors_cnt: int,
|
||||
sectors_count: int,
|
||||
media_type: int,
|
||||
sec_per_track: int,
|
||||
num_heads: int,
|
||||
hidden_sectors: int,
|
||||
volume_label: str,
|
||||
file_sys_type: str,
|
||||
volume_uuid: int = -1) -> None:
|
||||
self.oem_name: str = oem_name
|
||||
self.sector_size: int = sector_size
|
||||
assert self.sector_size in ALLOWED_SECTOR_SIZES
|
||||
self.sectors_per_cluster: int = sectors_per_cluster
|
||||
self.reserved_sectors_cnt: int = reserved_sectors_cnt
|
||||
self.fat_tables_cnt: int = fat_tables_cnt
|
||||
self.root_dir_sectors_cnt: int = root_dir_sectors_cnt
|
||||
self.sectors_count: int = sectors_count
|
||||
self.media_type: int = media_type
|
||||
self.sectors_per_fat_cnt = get_fat_sectors_count(self.size // self.sector_size, self.sector_size)
|
||||
self.sec_per_track: int = sec_per_track
|
||||
self.num_heads: int = num_heads
|
||||
self.hidden_sectors: int = hidden_sectors
|
||||
self.volume_label: str = volume_label
|
||||
self.file_sys_type: str = file_sys_type
|
||||
self.volume_uuid: int = volume_uuid
|
||||
self._binary_image: bytearray = bytearray(b'')
|
||||
|
||||
@property
|
||||
def binary_image(self) -> bytearray:
|
||||
return self._binary_image
|
||||
|
||||
@binary_image.setter
|
||||
def binary_image(self, value: bytearray) -> None:
|
||||
self._binary_image = value
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return self.sector_size * self.sectors_count
|
||||
|
||||
@property
|
||||
def data_region_start(self) -> int:
|
||||
return self.non_data_sectors * self.sector_size
|
||||
|
||||
@property
|
||||
def fatfs_type(self) -> int:
|
||||
# variable typed_fatfs_type must be explicitly typed to avoid mypy error
|
||||
typed_fatfs_type: int = get_fatfs_type(self.clusters)
|
||||
return typed_fatfs_type
|
||||
|
||||
@property
|
||||
def clusters(self) -> int:
|
||||
"""
|
||||
The actual number of clusters is calculated by `number_of_clusters`,
|
||||
however, the initial two blocks of FAT are reserved (device type and root directory),
|
||||
despite they don't refer to the data region.
|
||||
Since that, two clusters are added to use the full potential of the FAT file system partition.
|
||||
"""
|
||||
clusters_cnt_: int = number_of_clusters(self.data_sectors, self.sectors_per_cluster) + RESERVED_CLUSTERS_COUNT
|
||||
return clusters_cnt_
|
||||
|
||||
@property
|
||||
def data_sectors(self) -> int:
|
||||
# self.sector_size is checked in constructor if has one of allowed values (ALLOWED_SECTOR_SIZES)
|
||||
return (self.size // self.sector_size) - self.non_data_sectors
|
||||
|
||||
@property
|
||||
def non_data_sectors(self) -> int:
|
||||
non_data_sectors_: int = get_non_data_sectors_cnt(self.reserved_sectors_cnt,
|
||||
self.sectors_per_fat_cnt,
|
||||
self.root_dir_sectors_cnt)
|
||||
return non_data_sectors_
|
||||
|
||||
@property
|
||||
def fat_table_start_address(self) -> int:
|
||||
return self.sector_size * self.reserved_sectors_cnt
|
||||
|
||||
@property
|
||||
def entries_root_count(self) -> int:
|
||||
entries_root_count_: int = (self.root_dir_sectors_cnt * self.sector_size) // FATDefaults.ENTRY_SIZE
|
||||
return entries_root_count_
|
||||
|
||||
@property
|
||||
def root_directory_start(self) -> int:
|
||||
root_dir_start: int = (self.reserved_sectors_cnt + self.sectors_per_fat_cnt) * self.sector_size
|
||||
return root_dir_start
|
||||
@@ -0,0 +1,343 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
from .entry import Entry
|
||||
from .exceptions import FatalError, WriteDirectoryException
|
||||
from .fat import FAT, Cluster
|
||||
from .fatfs_state import FATFSState
|
||||
from .long_filename_utils import (build_lfn_full_name, build_lfn_unique_entry_name_order,
|
||||
get_required_lfn_entries_count, split_name_to_lfn_entries,
|
||||
split_name_to_lfn_entry_blocks)
|
||||
from .utils import (DATETIME, INVALID_SFN_CHARS_PATTERN, MAX_EXT_SIZE, MAX_NAME_SIZE, FATDefaults,
|
||||
build_lfn_short_entry_name, build_name, lfn_checksum, required_clusters_count,
|
||||
split_content_into_sectors, split_to_name_and_extension)
|
||||
|
||||
|
||||
class File:
|
||||
"""
|
||||
The class File provides API to write into the files. It represents file in the FS.
|
||||
"""
|
||||
ATTR_ARCHIVE: int = 0x20
|
||||
ENTITY_TYPE: int = ATTR_ARCHIVE
|
||||
|
||||
def __init__(self, name: str, fat: FAT, fatfs_state: FATFSState, entry: Entry, extension: str = '') -> None:
|
||||
self.name: str = name
|
||||
self.extension: str = extension
|
||||
self.fatfs_state: FATFSState = fatfs_state
|
||||
self.fat: FAT = fat
|
||||
self.size: int = 0
|
||||
self._first_cluster: Optional[Cluster] = None
|
||||
self._entry: Entry = entry
|
||||
|
||||
@property
|
||||
def entry(self) -> Entry:
|
||||
return self._entry
|
||||
|
||||
@property
|
||||
def first_cluster(self) -> Optional[Cluster]:
|
||||
return self._first_cluster
|
||||
|
||||
@first_cluster.setter
|
||||
def first_cluster(self, value: Cluster) -> None:
|
||||
self._first_cluster = value
|
||||
|
||||
def name_equals(self, name: str, extension: str) -> bool:
|
||||
equals_: bool = build_name(name, extension) == build_name(self.name, self.extension)
|
||||
return equals_
|
||||
|
||||
def write(self, content: bytes) -> None:
|
||||
self.entry.update_content_size(len(content))
|
||||
# we assume that the correct amount of clusters is allocated
|
||||
current_cluster = self._first_cluster
|
||||
for content_part in split_content_into_sectors(content, self.fatfs_state.boot_sector_state.sector_size):
|
||||
content_as_list = content_part
|
||||
if current_cluster is None:
|
||||
raise FatalError('No free space left!')
|
||||
|
||||
address: int = current_cluster.cluster_data_address
|
||||
self.fatfs_state.binary_image[address: address + len(content_part)] = content_as_list
|
||||
current_cluster = current_cluster.next_cluster
|
||||
|
||||
|
||||
class Directory:
|
||||
"""
|
||||
The Directory class provides API to add files and directories into the directory
|
||||
and to find the file according to path and write it.
|
||||
"""
|
||||
ATTR_DIRECTORY: int = 0x10
|
||||
ATTR_ARCHIVE: int = 0x20
|
||||
ENTITY_TYPE: int = ATTR_DIRECTORY
|
||||
|
||||
CURRENT_DIRECTORY = '.'
|
||||
PARENT_DIRECTORY = '..'
|
||||
|
||||
def __init__(self,
|
||||
name,
|
||||
fat,
|
||||
fatfs_state,
|
||||
entry=None,
|
||||
cluster=None,
|
||||
size=None,
|
||||
extension='',
|
||||
parent=None):
|
||||
# type: (str, FAT, FATFSState, Optional[Entry], Cluster, Optional[int], str, Directory) -> None
|
||||
self.name: str = name
|
||||
self.fatfs_state: FATFSState = fatfs_state
|
||||
self.extension: str = extension
|
||||
|
||||
self.fat: FAT = fat
|
||||
self.size: int = size or self.fatfs_state.boot_sector_state.sector_size
|
||||
|
||||
# if directory is root its parent is itself
|
||||
self.parent: Directory = parent or self
|
||||
self._first_cluster: Cluster = cluster
|
||||
|
||||
# entries will be initialized after the cluster allocation
|
||||
self.entries: List[Entry] = []
|
||||
self.entities: List[Union[File, Directory]] = [] # type: ignore
|
||||
self._entry = entry # currently not in use (will use later for e.g. modification time, etc.)
|
||||
|
||||
@property
|
||||
def is_root(self) -> bool:
|
||||
return self.parent is self
|
||||
|
||||
@property
|
||||
def first_cluster(self) -> Cluster:
|
||||
return self._first_cluster
|
||||
|
||||
@first_cluster.setter
|
||||
def first_cluster(self, value: Cluster) -> None:
|
||||
self._first_cluster = value
|
||||
|
||||
def name_equals(self, name: str, extension: str) -> bool:
|
||||
equals_: bool = build_name(name, extension) == build_name(self.name, self.extension)
|
||||
return equals_
|
||||
|
||||
@property
|
||||
def entries_count(self) -> int:
|
||||
entries_count_: int = self.size // FATDefaults.ENTRY_SIZE
|
||||
return entries_count_
|
||||
|
||||
def create_entries(self, cluster: Cluster) -> List[Entry]:
|
||||
return [Entry(entry_id=i,
|
||||
parent_dir_entries_address=cluster.cluster_data_address,
|
||||
fatfs_state=self.fatfs_state)
|
||||
for i in range(self.entries_count)]
|
||||
|
||||
def init_directory(self) -> None:
|
||||
self.entries = self.create_entries(self._first_cluster)
|
||||
|
||||
# the root directory doesn't contain link to itself nor the parent
|
||||
if self.is_root:
|
||||
return
|
||||
# if the directory is not root we initialize the reference to itself and to the parent directory
|
||||
for dir_id, name_ in ((self, self.CURRENT_DIRECTORY), (self.parent, self.PARENT_DIRECTORY)):
|
||||
new_dir_: Entry = self.find_free_entry() or self.chain_directory()
|
||||
new_dir_.allocate_entry(first_cluster_id=dir_id.first_cluster.id,
|
||||
entity_name=name_,
|
||||
entity_extension='',
|
||||
entity_type=dir_id.ENTITY_TYPE)
|
||||
|
||||
def lookup_entity(self, object_name: str, extension: str): # type: ignore
|
||||
for entity in self.entities:
|
||||
if build_name(entity.name, entity.extension) == build_name(object_name, extension):
|
||||
return entity
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _is_end_of_path(path_as_list: List[str]) -> bool:
|
||||
"""
|
||||
:param path_as_list: path split into the list
|
||||
|
||||
:returns: True if the file is the leaf of the directory tree, False otherwise
|
||||
The method is part of the base of recursion,
|
||||
determines if the path is target file or directory in the tree folder structure.
|
||||
"""
|
||||
return len(path_as_list) == 1
|
||||
|
||||
def recursive_search(self, path_as_list, current_dir): # type: ignore
|
||||
name, extension = split_to_name_and_extension(path_as_list[0])
|
||||
next_obj = current_dir.lookup_entity(name, extension)
|
||||
if next_obj is None:
|
||||
raise FileNotFoundError('No such file or directory!')
|
||||
if self._is_end_of_path(path_as_list) and next_obj.name_equals(name, extension):
|
||||
return next_obj
|
||||
return self.recursive_search(path_as_list[1:], next_obj)
|
||||
|
||||
def find_free_entry(self) -> Optional[Entry]:
|
||||
for entry in self.entries:
|
||||
if entry.is_empty:
|
||||
return entry
|
||||
return None
|
||||
|
||||
def _extend_directory(self) -> None:
|
||||
current: Cluster = self.first_cluster
|
||||
while current.next_cluster is not None:
|
||||
current = current.next_cluster
|
||||
new_cluster: Cluster = self.fat.find_free_cluster()
|
||||
current.set_in_fat(new_cluster.id)
|
||||
assert current is not new_cluster
|
||||
current.next_cluster = new_cluster
|
||||
self.entries += self.create_entries(new_cluster)
|
||||
|
||||
def chain_directory(self) -> Entry:
|
||||
"""
|
||||
:returns: First free entry
|
||||
|
||||
The method adds new Cluster to the Directory and returns first free entry.
|
||||
"""
|
||||
self._extend_directory()
|
||||
free_entry: Entry = self.find_free_entry()
|
||||
if free_entry is None:
|
||||
raise FatalError('No more space left!')
|
||||
return free_entry
|
||||
|
||||
@staticmethod
|
||||
def allocate_long_name_object(free_entry,
|
||||
name,
|
||||
extension,
|
||||
target_dir,
|
||||
free_cluster_id,
|
||||
entity_type,
|
||||
date,
|
||||
time):
|
||||
# type: (Entry, str, str, Directory, int, int, DATETIME, DATETIME) -> Entry
|
||||
lfn_full_name: str = build_lfn_full_name(name, extension)
|
||||
lfn_unique_entry_order: int = build_lfn_unique_entry_name_order(target_dir.entities, name)
|
||||
lfn_short_entry_name: str = build_lfn_short_entry_name(name, extension, lfn_unique_entry_order)
|
||||
checksum: int = lfn_checksum(lfn_short_entry_name)
|
||||
entries_count: int = get_required_lfn_entries_count(lfn_full_name)
|
||||
|
||||
# entries in long file name entries chain starts with the last entry
|
||||
split_names_reversed = list(reversed(list(enumerate(split_name_to_lfn_entries(lfn_full_name, entries_count)))))
|
||||
for i, name_split_to_entry in split_names_reversed:
|
||||
order: int = i + 1
|
||||
blocks_: List[bytes] = split_name_to_lfn_entry_blocks(name_split_to_entry)
|
||||
lfn_names: List[bytes] = list(map(lambda x: x.lower(), blocks_))
|
||||
free_entry.allocate_entry(first_cluster_id=free_cluster_id,
|
||||
entity_name=name,
|
||||
entity_extension=extension,
|
||||
entity_type=entity_type,
|
||||
lfn_order=order,
|
||||
lfn_names=lfn_names,
|
||||
lfn_checksum_=checksum,
|
||||
lfn_is_last=order == entries_count)
|
||||
free_entry = target_dir.find_free_entry() or target_dir.chain_directory()
|
||||
free_entry.allocate_entry(first_cluster_id=free_cluster_id,
|
||||
entity_name=lfn_short_entry_name[:MAX_NAME_SIZE],
|
||||
entity_extension=lfn_short_entry_name[MAX_NAME_SIZE:],
|
||||
entity_type=entity_type,
|
||||
lfn_order=Entry.SHORT_ENTRY_LN,
|
||||
date=date,
|
||||
time=time)
|
||||
return free_entry
|
||||
|
||||
@staticmethod
|
||||
def _is_valid_sfn(name: str, extension: str) -> bool:
|
||||
if INVALID_SFN_CHARS_PATTERN.search(name) or INVALID_SFN_CHARS_PATTERN.search(name):
|
||||
return False
|
||||
ret: bool = len(name) <= MAX_NAME_SIZE and len(extension) <= MAX_EXT_SIZE
|
||||
return ret
|
||||
|
||||
def allocate_object(self,
|
||||
name,
|
||||
entity_type,
|
||||
object_timestamp_,
|
||||
path_from_root=None,
|
||||
extension='',
|
||||
is_empty=False):
|
||||
# type: (str, int, datetime, Optional[List[str]], str, bool) -> Tuple[Cluster, Entry, Directory]
|
||||
"""
|
||||
Method finds the target directory in the path
|
||||
and allocates cluster (both the record in FAT and cluster in the data region)
|
||||
and entry in the specified directory
|
||||
"""
|
||||
|
||||
free_cluster: Optional[Cluster] = None
|
||||
free_cluster_id = 0x00
|
||||
if not is_empty:
|
||||
free_cluster = self.fat.find_free_cluster()
|
||||
free_cluster_id = free_cluster.id
|
||||
|
||||
target_dir: Directory = self if not path_from_root else self.recursive_search(path_from_root, self)
|
||||
free_entry: Entry = target_dir.find_free_entry() or target_dir.chain_directory()
|
||||
|
||||
fatfs_date_ = (object_timestamp_.year, object_timestamp_.month, object_timestamp_.day)
|
||||
fatfs_time_ = (object_timestamp_.hour, object_timestamp_.minute, object_timestamp_.second)
|
||||
|
||||
if not self.fatfs_state.long_names_enabled or self._is_valid_sfn(name, extension):
|
||||
free_entry.allocate_entry(first_cluster_id=free_cluster_id,
|
||||
entity_name=name,
|
||||
entity_extension=extension,
|
||||
date=fatfs_date_,
|
||||
time=fatfs_time_,
|
||||
fits_short=True,
|
||||
entity_type=entity_type)
|
||||
return free_cluster, free_entry, target_dir
|
||||
return free_cluster, self.allocate_long_name_object(free_entry=free_entry,
|
||||
name=name,
|
||||
extension=extension,
|
||||
target_dir=target_dir,
|
||||
free_cluster_id=free_cluster_id,
|
||||
entity_type=entity_type,
|
||||
date=fatfs_date_,
|
||||
time=fatfs_time_), target_dir
|
||||
|
||||
def new_file(self,
|
||||
name: str,
|
||||
extension: str,
|
||||
path_from_root: Optional[List[str]],
|
||||
object_timestamp_: datetime,
|
||||
is_empty: bool) -> None:
|
||||
free_cluster, free_entry, target_dir = self.allocate_object(name=name,
|
||||
extension=extension,
|
||||
entity_type=Directory.ATTR_ARCHIVE,
|
||||
path_from_root=path_from_root,
|
||||
object_timestamp_=object_timestamp_,
|
||||
is_empty=is_empty)
|
||||
|
||||
file: File = File(name=name,
|
||||
fat=self.fat,
|
||||
extension=extension,
|
||||
fatfs_state=self.fatfs_state,
|
||||
entry=free_entry)
|
||||
file.first_cluster = free_cluster
|
||||
target_dir.entities.append(file)
|
||||
|
||||
def new_directory(self, name, parent, path_from_root, object_timestamp_):
|
||||
# type: (str, Directory, Optional[List[str]], datetime) -> None
|
||||
free_cluster, free_entry, target_dir = self.allocate_object(name=name,
|
||||
entity_type=Directory.ATTR_DIRECTORY,
|
||||
path_from_root=path_from_root,
|
||||
object_timestamp_=object_timestamp_)
|
||||
|
||||
directory: Directory = Directory(name=name,
|
||||
fat=self.fat,
|
||||
parent=parent,
|
||||
fatfs_state=self.fatfs_state,
|
||||
entry=free_entry)
|
||||
directory.first_cluster = free_cluster
|
||||
directory.init_directory()
|
||||
target_dir.entities.append(directory)
|
||||
|
||||
def write_to_file(self, path: List[str], content: bytes) -> None:
|
||||
"""
|
||||
Writes to file existing in the directory structure.
|
||||
|
||||
:param path: path split into the list
|
||||
:param content: content as a string to be written into a file
|
||||
:returns: None
|
||||
:raises WriteDirectoryException: raised is the target object for writing is a directory
|
||||
"""
|
||||
entity_to_write: Entry = self.recursive_search(path, self)
|
||||
if isinstance(entity_to_write, File):
|
||||
clusters_cnt: int = required_clusters_count(cluster_size=self.fatfs_state.boot_sector_state.sector_size,
|
||||
content=content)
|
||||
self.fat.allocate_chain(entity_to_write.first_cluster, clusters_cnt)
|
||||
entity_to_write.write(content)
|
||||
else:
|
||||
raise WriteDirectoryException(f'`{os.path.join(*path)}` is a directory!')
|
||||
@@ -0,0 +1,98 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
from typing import List
|
||||
|
||||
from .entry import Entry
|
||||
from .exceptions import NoFreeClusterException
|
||||
from .utils import build_name, convert_to_utf16_and_pad
|
||||
|
||||
# File name with long filenames support can be as long as memory allows. It is split into entries
|
||||
# holding 13 characters of the filename, thus the number of required entries is ceil(len(long_name) / 13).
|
||||
# This is computed using `get_required_lfn_entries_count`.
|
||||
# For creating long name entries we need to split the name by 13 characters using `split_name_to_lfn_entries`
|
||||
# and in every entry into three blocks with sizes 5, 6 and 2 characters using `split_name_to_lfn_entry`.
|
||||
|
||||
MAXIMAL_FILES_SAME_PREFIX: int = 127
|
||||
|
||||
|
||||
def get_required_lfn_entries_count(lfn_full_name: str) -> int:
|
||||
"""
|
||||
Compute the number of entries required to store the long name.
|
||||
One long filename entry can hold 13 characters with size 2 bytes.
|
||||
|
||||
E.g. "thisisverylongfilenama.txt" with length of 26 needs 2 lfn entries,
|
||||
but "thisisverylongfilenamax.txt" with 27 characters needs 3 lfn entries.
|
||||
"""
|
||||
entries_count: int = (len(lfn_full_name) + Entry.CHARS_PER_ENTRY - 1) // Entry.CHARS_PER_ENTRY
|
||||
return entries_count
|
||||
|
||||
|
||||
def split_name_to_lfn_entries(name: str, entries: int) -> List[str]:
|
||||
"""
|
||||
If the filename is longer than 8 (name) + 3 (extension) characters,
|
||||
generator uses long name structure and splits the name into suitable amount of blocks.
|
||||
|
||||
E.g. 'thisisverylongfilenama.txt' would be split to ['THISISVERYLON', 'GFILENAMA.TXT'],
|
||||
in case of 'thisisverylongfilenamax.txt' - ['THISISVERYLON', 'GFILENAMAX.TX', 'T']
|
||||
"""
|
||||
return [name[i * Entry.CHARS_PER_ENTRY:(i + 1) * Entry.CHARS_PER_ENTRY] for i in range(entries)]
|
||||
|
||||
|
||||
def split_name_to_lfn_entry_blocks(name: str) -> List[bytes]:
|
||||
"""
|
||||
Filename is divided into three blocks in every long file name entry. Sizes of the blocks are defined
|
||||
by LDIR_Name1_SIZE, LDIR_Name2_SIZE and LDIR_Name3_SIZE, thus every block contains LDIR_Name{X}_SIZE * 2 bytes.
|
||||
|
||||
If the filename ends in one of the blocks, it is terminated by zero encoded to two bytes (0x0000). Other unused
|
||||
characters are set to 0xFFFF.
|
||||
E.g.:
|
||||
'GFILENAMA.TXT' -> [b'G\x00F\x00I\x00L\x00E\x00', b'N\x00A\x00M\x00A\x00.\x00T\x00', b'X\x00T\x00'];
|
||||
'T' -> [b'T\x00\x00\x00\xff\xff\xff\xff\xff\xff', b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff',
|
||||
b'\xff\xff\xff\xff']
|
||||
|
||||
Notice that since every character is coded using 2 bytes be must add 0x00 to ASCII symbols ('G' -> 'G\x00', etc.),
|
||||
since character 'T' ends in the first block, we must add '\x00\x00' after 'T\x00'.
|
||||
"""
|
||||
max_entry_size: int = Entry.LDIR_Name1_SIZE + Entry.LDIR_Name2_SIZE + Entry.LDIR_Name2_SIZE
|
||||
assert len(name) <= max_entry_size
|
||||
blocks_: List[bytes] = [
|
||||
convert_to_utf16_and_pad(content=name[:Entry.LDIR_Name1_SIZE],
|
||||
expected_size=Entry.LDIR_Name1_SIZE),
|
||||
convert_to_utf16_and_pad(content=name[Entry.LDIR_Name1_SIZE:Entry.LDIR_Name1_SIZE + Entry.LDIR_Name2_SIZE],
|
||||
expected_size=Entry.LDIR_Name2_SIZE),
|
||||
convert_to_utf16_and_pad(content=name[Entry.LDIR_Name1_SIZE + Entry.LDIR_Name2_SIZE:],
|
||||
expected_size=Entry.LDIR_Name3_SIZE)
|
||||
]
|
||||
return blocks_
|
||||
|
||||
|
||||
def build_lfn_unique_entry_name_order(entities: list, lfn_entry_name: str) -> int:
|
||||
"""
|
||||
The short entry contains only the first 6 characters of the file name,
|
||||
and we have to distinguish it from other names within the directory starting with the same 6 characters.
|
||||
To make it unique, we add its order in relation to other names such that lfn_entry_name[:6] == other[:6].
|
||||
The order is specified by the character, starting with chr(1).
|
||||
|
||||
E.g. the file in directory 'thisisverylongfilenama.txt' will be named 'THISIS~1TXT' in its short entry.
|
||||
If we add another file 'thisisverylongfilenamax.txt' its name in the short entry will be 'THISIS~2TXT'.
|
||||
"""
|
||||
preceding_entries: int = 1
|
||||
for entity in entities:
|
||||
if entity.name[:6] == lfn_entry_name[:6]:
|
||||
preceding_entries += 1
|
||||
if preceding_entries > MAXIMAL_FILES_SAME_PREFIX:
|
||||
raise NoFreeClusterException('Maximal number of files with the same prefix is 127')
|
||||
return preceding_entries
|
||||
|
||||
|
||||
def build_lfn_full_name(name: str, extension: str) -> str:
|
||||
"""
|
||||
The extension is optional, and the long filename entry explicitly specifies it,
|
||||
on the opposite as for short file names.
|
||||
"""
|
||||
lfn_record: str = build_name(name, extension)
|
||||
# the name must be terminated with NULL terminator
|
||||
# if it doesn't fit into the set of long name directory entries
|
||||
if len(lfn_record) % Entry.CHARS_PER_ENTRY != 0:
|
||||
return lfn_record + chr(0)
|
||||
return lfn_record
|
||||
@@ -0,0 +1,299 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from construct import BitsInteger, BitStruct, Int16ul
|
||||
|
||||
# the regex pattern defines symbols that are allowed by long file names but not by short file names
|
||||
INVALID_SFN_CHARS_PATTERN = re.compile(r'[.+,;=\[\]]')
|
||||
|
||||
FATFS_MIN_ALLOC_UNIT: int = 128
|
||||
FAT12_MAX_CLUSTERS: int = 4085
|
||||
FAT16_MAX_CLUSTERS: int = 65525
|
||||
RESERVED_CLUSTERS_COUNT: int = 2
|
||||
PAD_CHAR: int = 0x20
|
||||
FAT12: int = 12
|
||||
FAT16: int = 16
|
||||
FAT32: int = 32
|
||||
FULL_BYTE: bytes = b'\xff'
|
||||
EMPTY_BYTE: bytes = b'\x00'
|
||||
# redundant
|
||||
BYTES_PER_DIRECTORY_ENTRY: int = 32
|
||||
UINT32_MAX: int = (1 << 32) - 1
|
||||
MAX_NAME_SIZE: int = 8
|
||||
MAX_EXT_SIZE: int = 3
|
||||
DATETIME = Tuple[int, int, int]
|
||||
FATFS_INCEPTION_YEAR: int = 1980
|
||||
|
||||
FATFS_INCEPTION: datetime = datetime(FATFS_INCEPTION_YEAR, 1, 1, 0, 0, 0, 0)
|
||||
|
||||
FATFS_MAX_HOURS = 24
|
||||
FATFS_MAX_MINUTES = 60
|
||||
FATFS_MAX_SECONDS = 60
|
||||
|
||||
FATFS_MAX_DAYS = 31
|
||||
FATFS_MAX_MONTHS = 12
|
||||
FATFS_MAX_YEARS = 127
|
||||
|
||||
FATFS_SECONDS_GRANULARITY: int = 2
|
||||
|
||||
# long names are encoded to two bytes in utf-16
|
||||
LONG_NAMES_ENCODING: str = 'utf-16'
|
||||
SHORT_NAMES_ENCODING: str = 'utf-8'
|
||||
|
||||
# compatible with WL_SECTOR_SIZE
|
||||
# choices for WL are WL_SECTOR_SIZE_512 and WL_SECTOR_SIZE_4096
|
||||
ALLOWED_WL_SECTOR_SIZES: List[int] = [512, 4096]
|
||||
ALLOWED_SECTOR_SIZES: List[int] = [512, 1024, 2048, 4096]
|
||||
|
||||
ALLOWED_SECTORS_PER_CLUSTER: List[int] = [1, 2, 4, 8, 16, 32, 64, 128]
|
||||
|
||||
|
||||
def crc32(input_values: List[int], crc: int) -> int:
|
||||
"""
|
||||
Name Polynomial Reversed? Init-value XOR-out
|
||||
crc32 0x104C11DB7 True 4294967295 (UINT32_MAX) 0xFFFFFFFF
|
||||
"""
|
||||
return binascii.crc32(bytearray(input_values), crc)
|
||||
|
||||
|
||||
def number_of_clusters(number_of_sectors: int, sectors_per_cluster: int) -> int:
|
||||
return number_of_sectors // sectors_per_cluster
|
||||
|
||||
|
||||
def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, root_dir_sectors_cnt: int) -> int:
|
||||
return reserved_sectors_cnt + sectors_per_fat_cnt + root_dir_sectors_cnt
|
||||
|
||||
|
||||
def get_fatfs_type(clusters_count: int) -> int:
|
||||
if clusters_count < FAT12_MAX_CLUSTERS:
|
||||
return FAT12
|
||||
if clusters_count <= FAT16_MAX_CLUSTERS:
|
||||
return FAT16
|
||||
return FAT32
|
||||
|
||||
|
||||
def get_fat_sectors_count(clusters_count: int, sector_size: int) -> int:
|
||||
fatfs_type_ = get_fatfs_type(clusters_count)
|
||||
if fatfs_type_ == FAT32:
|
||||
raise NotImplementedError('FAT32 is not supported!')
|
||||
# number of byte halves
|
||||
cluster_s: int = fatfs_type_ // 4
|
||||
fat_size_bytes: int = (
|
||||
clusters_count * 2 + cluster_s) if fatfs_type_ == FAT16 else (clusters_count * 3 + 1) // 2 + cluster_s
|
||||
return (fat_size_bytes + sector_size - 1) // sector_size
|
||||
|
||||
|
||||
def required_clusters_count(cluster_size: int, content: bytes) -> int:
|
||||
# compute number of required clusters for file text
|
||||
return (len(content) + cluster_size - 1) // cluster_size
|
||||
|
||||
|
||||
def generate_4bytes_random() -> int:
|
||||
return uuid.uuid4().int & 0xFFFFFFFF
|
||||
|
||||
|
||||
def pad_string(content: str, size: Optional[int] = None, pad: int = PAD_CHAR) -> str:
|
||||
# cut string if longer and fill with pad character if shorter than size
|
||||
return content.ljust(size or len(content), chr(pad))[:size]
|
||||
|
||||
|
||||
def right_strip_string(content: str, pad: int = PAD_CHAR) -> str:
|
||||
return content.rstrip(chr(pad))
|
||||
|
||||
|
||||
def build_lfn_short_entry_name(name: str, extension: str, order: int) -> str:
|
||||
return '{}{}'.format(pad_string(content=name[:MAX_NAME_SIZE - 2] + '~' + chr(order), size=MAX_NAME_SIZE),
|
||||
pad_string(extension[:MAX_EXT_SIZE], size=MAX_EXT_SIZE))
|
||||
|
||||
|
||||
def lfn_checksum(short_entry_name: str) -> int:
|
||||
"""
|
||||
Function defined by FAT specification. Computes checksum out of name in the short file name entry.
|
||||
"""
|
||||
checksum_result = 0
|
||||
for i in range(MAX_NAME_SIZE + MAX_EXT_SIZE):
|
||||
# operation is a right rotation on 8 bits (Python equivalent for unsigned char in C)
|
||||
checksum_result = (0x80 if checksum_result & 1 else 0x00) + (checksum_result >> 1) + ord(short_entry_name[i])
|
||||
checksum_result &= 0xff
|
||||
return checksum_result
|
||||
|
||||
|
||||
def convert_to_utf16_and_pad(content: str,
|
||||
expected_size: int,
|
||||
pad: bytes = FULL_BYTE) -> bytes:
|
||||
# we need to get rid of the Byte order mark 0xfeff or 0xfffe, fatfs does not use it
|
||||
bom_utf16: bytes = b'\xfe\xff'
|
||||
encoded_content_utf16: bytes = content.encode(LONG_NAMES_ENCODING)[len(bom_utf16):]
|
||||
return encoded_content_utf16.ljust(2 * expected_size, pad)
|
||||
|
||||
|
||||
def split_to_name_and_extension(full_name: str) -> Tuple[str, str]:
|
||||
name, extension = os.path.splitext(full_name)
|
||||
return name, extension.replace('.', '')
|
||||
|
||||
|
||||
def is_valid_fatfs_name(string: str) -> bool:
|
||||
return string == string.upper()
|
||||
|
||||
|
||||
def split_by_half_byte_12_bit_little_endian(value: int) -> Tuple[int, int, int]:
|
||||
value_as_bytes: bytes = Int16ul.build(value)
|
||||
return value_as_bytes[0] & 0x0f, value_as_bytes[0] >> 4, value_as_bytes[1] & 0x0f
|
||||
|
||||
|
||||
def merge_by_half_byte_12_bit_little_endian(v1: int, v2: int, v3: int) -> int:
|
||||
return v1 | v2 << 4 | v3 << 8
|
||||
|
||||
|
||||
def build_byte(first_half: int, second_half: int) -> int:
|
||||
return (first_half << 4) | second_half
|
||||
|
||||
|
||||
def split_content_into_sectors(content: bytes, sector_size: int) -> List[bytes]:
|
||||
result = []
|
||||
clusters_cnt: int = required_clusters_count(cluster_size=sector_size, content=content)
|
||||
|
||||
for i in range(clusters_cnt):
|
||||
result.append(content[sector_size * i:(i + 1) * sector_size])
|
||||
return result
|
||||
|
||||
|
||||
def get_args_for_partition_generator(desc: str, wl: bool) -> argparse.Namespace:
|
||||
parser: argparse.ArgumentParser = argparse.ArgumentParser(description=desc)
|
||||
parser.add_argument('input_directory',
|
||||
help='Path to the directory that will be encoded into fatfs image')
|
||||
parser.add_argument('--output_file',
|
||||
default='fatfs_image.img',
|
||||
help='Filename of the generated fatfs image')
|
||||
parser.add_argument('--partition_size',
|
||||
default=FATDefaults.SIZE,
|
||||
help='Size of the partition in bytes.' +
|
||||
('' if wl else ' Use `--partition_size detect` for detecting the minimal partition size.')
|
||||
)
|
||||
parser.add_argument('--sector_size',
|
||||
default=FATDefaults.SECTOR_SIZE,
|
||||
type=int,
|
||||
choices=ALLOWED_WL_SECTOR_SIZES if wl else ALLOWED_SECTOR_SIZES,
|
||||
help='Size of the partition in bytes')
|
||||
parser.add_argument('--sectors_per_cluster',
|
||||
default=1,
|
||||
type=int,
|
||||
choices=ALLOWED_SECTORS_PER_CLUSTER,
|
||||
help='Number of sectors per cluster')
|
||||
parser.add_argument('--root_entry_count',
|
||||
default=FATDefaults.ROOT_ENTRIES_COUNT,
|
||||
help='Number of entries in the root directory')
|
||||
parser.add_argument('--long_name_support',
|
||||
action='store_true',
|
||||
help='Set flag to enable long names support.')
|
||||
parser.add_argument('--use_default_datetime',
|
||||
action='store_true',
|
||||
help='For test purposes. If the flag is set the files are created with '
|
||||
'the default timestamp that is the 1st of January 1980')
|
||||
parser.add_argument('--fat_type',
|
||||
default=0,
|
||||
type=int,
|
||||
choices=[FAT12, FAT16, 0],
|
||||
help="""
|
||||
Type of fat. Select 12 for fat12, 16 for fat16. Don't set, or set to 0 for automatic
|
||||
calculation using cluster size and partition size.
|
||||
""")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.fat_type == 0:
|
||||
args.fat_type = None
|
||||
if args.partition_size == 'detect' and not wl:
|
||||
args.partition_size = -1
|
||||
args.partition_size = int(str(args.partition_size), 0)
|
||||
if not os.path.isdir(args.input_directory):
|
||||
raise NotADirectoryError(f'The target directory `{args.input_directory}` does not exist!')
|
||||
return args
|
||||
|
||||
|
||||
def read_filesystem(path: str) -> bytearray:
|
||||
with open(path, 'rb') as fs_file:
|
||||
return bytearray(fs_file.read())
|
||||
|
||||
|
||||
DATE_ENTRY = BitStruct(
|
||||
'year' / BitsInteger(7),
|
||||
'month' / BitsInteger(4),
|
||||
'day' / BitsInteger(5))
|
||||
|
||||
TIME_ENTRY = BitStruct(
|
||||
'hour' / BitsInteger(5),
|
||||
'minute' / BitsInteger(6),
|
||||
'second' / BitsInteger(5),
|
||||
)
|
||||
|
||||
|
||||
def build_name(name: str, extension: str) -> str:
|
||||
return f'{name}.{extension}' if len(extension) > 0 else name
|
||||
|
||||
|
||||
def build_date_entry(year: int, mon: int, mday: int) -> int:
|
||||
"""
|
||||
:param year: denotes year starting from 1980 (0 ~ 1980, 1 ~ 1981, etc), valid values are 1980 + 0..127 inclusive
|
||||
thus theoretically 1980 - 2107
|
||||
:param mon: denotes number of month of year in common order (1 ~ January, 2 ~ February, etc.),
|
||||
valid values: 1..12 inclusive
|
||||
:param mday: denotes number of day in month, valid values are 1..31 inclusive
|
||||
|
||||
:returns: 16 bit integer number (7 bits for year, 4 bits for month and 5 bits for day of the month)
|
||||
"""
|
||||
assert year in range(FATFS_INCEPTION_YEAR, FATFS_INCEPTION_YEAR + FATFS_MAX_YEARS)
|
||||
assert mon in range(1, FATFS_MAX_MONTHS + 1)
|
||||
assert mday in range(1, FATFS_MAX_DAYS + 1)
|
||||
return int.from_bytes(DATE_ENTRY.build(dict(year=year - FATFS_INCEPTION_YEAR, month=mon, day=mday)), 'big')
|
||||
|
||||
|
||||
def build_time_entry(hour: int, minute: int, sec: int) -> int:
|
||||
"""
|
||||
:param hour: denotes number of hour, valid values are 0..23 inclusive
|
||||
:param minute: denotes minutes, valid range 0..59 inclusive
|
||||
:param sec: denotes seconds with granularity 2 seconds (e.g. 1 ~ 2, 29 ~ 58), valid range 0..29 inclusive
|
||||
|
||||
:returns: 16 bit integer number (5 bits for hour, 6 bits for minute and 5 bits for second)
|
||||
"""
|
||||
assert hour in range(FATFS_MAX_HOURS)
|
||||
assert minute in range(FATFS_MAX_MINUTES)
|
||||
assert sec in range(FATFS_MAX_SECONDS)
|
||||
return int.from_bytes(TIME_ENTRY.build(
|
||||
dict(hour=hour, minute=minute, second=sec // FATFS_SECONDS_GRANULARITY)),
|
||||
byteorder='big'
|
||||
)
|
||||
|
||||
|
||||
class FATDefaults:
|
||||
# FATFS defaults
|
||||
SIZE: int = 1024 * 1024
|
||||
RESERVED_SECTORS_COUNT: int = 1
|
||||
FAT_TABLES_COUNT: int = 1
|
||||
SECTORS_PER_CLUSTER: int = 1
|
||||
SECTOR_SIZE: int = 0x1000
|
||||
HIDDEN_SECTORS: int = 0
|
||||
ENTRY_SIZE: int = 32
|
||||
NUM_HEADS: int = 0xff
|
||||
OEM_NAME: str = 'MSDOS5.0'
|
||||
SEC_PER_TRACK: int = 0x3f
|
||||
VOLUME_LABEL: str = 'Espressif'
|
||||
FILE_SYS_TYPE: str = 'FAT'
|
||||
ROOT_ENTRIES_COUNT: int = 512 # number of entries in the root directory, recommended 512
|
||||
MEDIA_TYPE: int = 0xf8
|
||||
SIGNATURE_WORD: bytes = b'\x55\xAA'
|
||||
|
||||
# wear levelling defaults
|
||||
VERSION: int = 2
|
||||
TEMP_BUFFER_SIZE: int = 32
|
||||
UPDATE_RATE: int = 16
|
||||
WR_SIZE: int = 16
|
||||
# wear leveling metadata (config sector) contains always sector size 4096
|
||||
WL_SECTOR_SIZE: int = 4096
|
||||
Executable
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from fatfs_utils.boot_sector import BootSector
|
||||
from fatfs_utils.exceptions import NoFreeClusterException
|
||||
from fatfs_utils.fat import FAT
|
||||
from fatfs_utils.fatfs_state import FATFSState
|
||||
from fatfs_utils.fs_object import Directory
|
||||
from fatfs_utils.long_filename_utils import get_required_lfn_entries_count
|
||||
from fatfs_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FATFS_INCEPTION, FATFS_MIN_ALLOC_UNIT,
|
||||
RESERVED_CLUSTERS_COUNT, FATDefaults, get_args_for_partition_generator,
|
||||
get_fat_sectors_count, get_non_data_sectors_cnt, read_filesystem,
|
||||
required_clusters_count)
|
||||
|
||||
|
||||
class FATFS:
|
||||
"""
|
||||
The class FATFS provides API for generating FAT file system.
|
||||
It contains reference to the FAT table and to the root directory.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
binary_image_path: Optional[str] = None,
|
||||
size: int = FATDefaults.SIZE,
|
||||
reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT,
|
||||
fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT,
|
||||
sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER,
|
||||
sector_size: int = FATDefaults.SECTOR_SIZE,
|
||||
hidden_sectors: int = FATDefaults.HIDDEN_SECTORS,
|
||||
long_names_enabled: bool = False,
|
||||
use_default_datetime: bool = True,
|
||||
num_heads: int = FATDefaults.NUM_HEADS,
|
||||
oem_name: str = FATDefaults.OEM_NAME,
|
||||
sec_per_track: int = FATDefaults.SEC_PER_TRACK,
|
||||
volume_label: str = FATDefaults.VOLUME_LABEL,
|
||||
file_sys_type: str = FATDefaults.FILE_SYS_TYPE,
|
||||
root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT,
|
||||
explicit_fat_type: int = None,
|
||||
media_type: int = FATDefaults.MEDIA_TYPE) -> None:
|
||||
# root directory bytes should be aligned by sector size
|
||||
assert (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0
|
||||
# number of bytes in the root dir must be even multiple of BPB_BytsPerSec
|
||||
assert ((root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0
|
||||
|
||||
root_dir_sectors_cnt: int = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size
|
||||
|
||||
self.state: FATFSState = FATFSState(sector_size=sector_size,
|
||||
explicit_fat_type=explicit_fat_type,
|
||||
reserved_sectors_cnt=reserved_sectors_cnt,
|
||||
root_dir_sectors_cnt=root_dir_sectors_cnt,
|
||||
size=size,
|
||||
file_sys_type=file_sys_type,
|
||||
num_heads=num_heads,
|
||||
fat_tables_cnt=fat_tables_cnt,
|
||||
sectors_per_cluster=sectors_per_cluster,
|
||||
media_type=media_type,
|
||||
hidden_sectors=hidden_sectors,
|
||||
sec_per_track=sec_per_track,
|
||||
long_names_enabled=long_names_enabled,
|
||||
volume_label=volume_label,
|
||||
oem_name=oem_name,
|
||||
use_default_datetime=use_default_datetime)
|
||||
binary_image: bytes = bytearray(
|
||||
read_filesystem(binary_image_path) if binary_image_path else self.create_empty_fatfs())
|
||||
self.state.binary_image = binary_image
|
||||
|
||||
self.fat: FAT = FAT(boot_sector_state=self.state.boot_sector_state, init_=True)
|
||||
|
||||
root_dir_size = self.state.boot_sector_state.root_dir_sectors_cnt * self.state.boot_sector_state.sector_size
|
||||
self.root_directory: Directory = Directory(name='A', # the name is not important, must be string
|
||||
size=root_dir_size,
|
||||
fat=self.fat,
|
||||
cluster=self.fat.clusters[1],
|
||||
fatfs_state=self.state)
|
||||
self.root_directory.init_directory()
|
||||
|
||||
def create_file(self, name: str,
|
||||
extension: str = '',
|
||||
path_from_root: Optional[List[str]] = None,
|
||||
object_timestamp_: datetime = FATFS_INCEPTION,
|
||||
is_empty: bool = False) -> None:
|
||||
"""
|
||||
Root directory recursively finds the parent directory of the new file, allocates cluster,
|
||||
entry and appends a new file into the parent directory.
|
||||
|
||||
When path_from_root is None the dir is root.
|
||||
|
||||
:param name: The name of the file.
|
||||
:param extension: The extension of the file.
|
||||
:param path_from_root: List of strings containing names of the ancestor directories in the given order.
|
||||
:param object_timestamp_: is not None, this will be propagated to the file's entry
|
||||
:param is_empty: True if there is no need to allocate any cluster, otherwise False
|
||||
"""
|
||||
self.root_directory.new_file(name=name,
|
||||
extension=extension,
|
||||
path_from_root=path_from_root,
|
||||
object_timestamp_=object_timestamp_,
|
||||
is_empty=is_empty)
|
||||
|
||||
def create_directory(self, name: str,
|
||||
path_from_root: Optional[List[str]] = None,
|
||||
object_timestamp_: datetime = FATFS_INCEPTION) -> None:
|
||||
"""
|
||||
Initially recursively finds a parent of the new directory
|
||||
and then create a new directory inside the parent.
|
||||
|
||||
When path_from_root is None the parent dir is root.
|
||||
|
||||
:param name: The full name of the directory (excluding its path)
|
||||
:param path_from_root: List of strings containing names of the ancestor directories in the given order.
|
||||
:param object_timestamp_: in case the user preserves the timestamps, this will be propagated to the
|
||||
metadata of the directory (to the corresponding entry)
|
||||
:returns: None
|
||||
"""
|
||||
parent_dir = self.root_directory
|
||||
if path_from_root:
|
||||
parent_dir = self.root_directory.recursive_search(path_from_root, self.root_directory)
|
||||
|
||||
self.root_directory.new_directory(name=name,
|
||||
parent=parent_dir,
|
||||
path_from_root=path_from_root,
|
||||
object_timestamp_=object_timestamp_)
|
||||
|
||||
def write_content(self, path_from_root: List[str], content: bytes) -> None:
|
||||
"""
|
||||
fat fs invokes root directory to recursively find the required file and writes the content
|
||||
"""
|
||||
self.root_directory.write_to_file(path_from_root, content)
|
||||
|
||||
def create_empty_fatfs(self) -> Any:
|
||||
boot_sector_ = BootSector(boot_sector_state=self.state.boot_sector_state)
|
||||
boot_sector_.generate_boot_sector()
|
||||
return boot_sector_.binary_image
|
||||
|
||||
def write_filesystem(self, output_path: str) -> None:
|
||||
with open(output_path, 'wb') as output:
|
||||
output.write(bytearray(self.state.binary_image))
|
||||
|
||||
def _generate_partition_from_folder(self,
|
||||
folder_relative_path: str,
|
||||
folder_path: str = '',
|
||||
is_dir: bool = False) -> None:
|
||||
"""
|
||||
Given path to folder and folder name recursively encodes folder into binary image.
|
||||
Used by method generate.
|
||||
"""
|
||||
real_path: str = os.path.join(folder_path, folder_relative_path)
|
||||
lower_path: str = folder_relative_path
|
||||
|
||||
folder_relative_path = folder_relative_path.upper()
|
||||
|
||||
normal_path = os.path.normpath(folder_relative_path)
|
||||
split_path = normal_path.split(os.sep)
|
||||
object_timestamp = datetime.fromtimestamp(os.path.getctime(real_path))
|
||||
|
||||
if os.path.isfile(real_path):
|
||||
with open(real_path, 'rb') as file:
|
||||
content = file.read()
|
||||
file_name, extension = os.path.splitext(split_path[-1])
|
||||
extension = extension[1:] # remove the dot from the extension
|
||||
self.create_file(name=file_name,
|
||||
extension=extension,
|
||||
path_from_root=split_path[1:-1] or None,
|
||||
object_timestamp_=object_timestamp,
|
||||
is_empty=len(content) == 0)
|
||||
self.write_content(split_path[1:], content)
|
||||
elif os.path.isdir(real_path):
|
||||
if not is_dir:
|
||||
self.create_directory(name=split_path[-1],
|
||||
path_from_root=split_path[1:-1],
|
||||
object_timestamp_=object_timestamp)
|
||||
|
||||
# sorting files for better testability
|
||||
dir_content = list(sorted(os.listdir(real_path)))
|
||||
for path in dir_content:
|
||||
self._generate_partition_from_folder(os.path.join(lower_path, path), folder_path=folder_path)
|
||||
|
||||
def generate(self, input_directory: str) -> None:
|
||||
"""
|
||||
Normalize path to folder and recursively encode folder to binary image
|
||||
"""
|
||||
path_to_folder, folder_name = os.path.split(input_directory)
|
||||
self._generate_partition_from_folder(folder_name, folder_path=path_to_folder, is_dir=True)
|
||||
|
||||
|
||||
def calculate_min_space(path: List[str],
|
||||
fs_entity: str,
|
||||
sector_size: int = 0x1000,
|
||||
long_file_names: bool = False,
|
||||
is_root: bool = False) -> int:
|
||||
if os.path.isfile(os.path.join(*path, fs_entity)):
|
||||
with open(os.path.join(*path, fs_entity), 'rb') as file_:
|
||||
content = file_.read()
|
||||
res: int = required_clusters_count(sector_size, content)
|
||||
return res
|
||||
buff: int = 0
|
||||
dir_size = 2 * FATDefaults.ENTRY_SIZE # record for symlinks "." and ".."
|
||||
for file in sorted(os.listdir(os.path.join(*path, fs_entity))):
|
||||
if long_file_names and True:
|
||||
# LFN entries + one short entry
|
||||
dir_size += (get_required_lfn_entries_count(fs_entity) + 1) * FATDefaults.ENTRY_SIZE
|
||||
else:
|
||||
dir_size += FATDefaults.ENTRY_SIZE
|
||||
buff += calculate_min_space(path + [fs_entity], file, sector_size, long_file_names, is_root=False)
|
||||
if is_root and dir_size // FATDefaults.ENTRY_SIZE > FATDefaults.ROOT_ENTRIES_COUNT:
|
||||
raise NoFreeClusterException('Not enough space in root!')
|
||||
|
||||
# roundup sectors, at least one is required
|
||||
buff += (dir_size + sector_size - 1) // sector_size
|
||||
return buff
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = get_args_for_partition_generator('Create a FAT filesystem and populate it with directory content', wl=False)
|
||||
|
||||
if args.partition_size == -1:
|
||||
clusters = calculate_min_space([], args.input_directory, args.sector_size, long_file_names=True, is_root=True)
|
||||
fats = get_fat_sectors_count(clusters, args.sector_size)
|
||||
root_dir_sectors = (FATDefaults.ROOT_ENTRIES_COUNT * FATDefaults.ENTRY_SIZE) // args.sector_size
|
||||
args.partition_size = max(FATFS_MIN_ALLOC_UNIT * args.sector_size,
|
||||
(clusters + fats + get_non_data_sectors_cnt(RESERVED_CLUSTERS_COUNT,
|
||||
fats,
|
||||
root_dir_sectors)
|
||||
) * args.sector_size
|
||||
)
|
||||
|
||||
fatfs = FATFS(sector_size=args.sector_size,
|
||||
sectors_per_cluster=args.sectors_per_cluster,
|
||||
size=args.partition_size,
|
||||
root_entry_count=args.root_entry_count,
|
||||
explicit_fat_type=args.fat_type,
|
||||
long_names_enabled=args.long_name_support,
|
||||
use_default_datetime=args.use_default_datetime)
|
||||
|
||||
fatfs.generate(args.input_directory)
|
||||
fatfs.write_filesystem(args.output_file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import construct
|
||||
from fatfs_utils.boot_sector import BootSector
|
||||
from fatfs_utils.entry import Entry
|
||||
from fatfs_utils.fat import FAT
|
||||
from fatfs_utils.fatfs_state import BootSectorState
|
||||
from fatfs_utils.utils import FULL_BYTE, LONG_NAMES_ENCODING, PAD_CHAR, FATDefaults, lfn_checksum, read_filesystem
|
||||
from wl_fatfsgen import remove_wl
|
||||
|
||||
|
||||
def build_file_name(name1: bytes, name2: bytes, name3: bytes) -> str:
|
||||
full_name_ = name1 + name2 + name3
|
||||
# need to strip empty bytes and null-terminating char ('\x00')
|
||||
return full_name_.rstrip(FULL_BYTE).decode(LONG_NAMES_ENCODING).rstrip('\x00')
|
||||
|
||||
|
||||
def get_obj_name(obj_: dict, directory_bytes_: bytes, entry_position_: int, lfn_checksum_: int) -> str:
|
||||
obj_ext_ = obj_['DIR_Name_ext'].rstrip(chr(PAD_CHAR))
|
||||
ext_ = f'.{obj_ext_}' if len(obj_ext_) > 0 else ''
|
||||
obj_name_: str = obj_['DIR_Name'].rstrip(chr(PAD_CHAR)) + ext_ # short entry name
|
||||
|
||||
# if LFN was detected, the record is considered as single SFN record only if DIR_NTRes == 0x18 (LDIR_DIR_NTRES)
|
||||
# if LFN was not detected, the record cannot be part of the LFN, no matter the value of DIR_NTRes
|
||||
if not args.long_name_support or obj_['DIR_NTRes'] == Entry.LDIR_DIR_NTRES:
|
||||
return obj_name_
|
||||
|
||||
full_name = {}
|
||||
|
||||
for pos in range(entry_position_ - 1, -1, -1): # loop from the current entry back to the start
|
||||
obj_address_: int = FATDefaults.ENTRY_SIZE * pos
|
||||
entry_bytes_: bytes = directory_bytes_[obj_address_: obj_address_ + FATDefaults.ENTRY_SIZE]
|
||||
struct_ = Entry.parse_entry_long(entry_bytes_, lfn_checksum_)
|
||||
if len(struct_.items()) > 0:
|
||||
full_name[struct_['order']] = build_file_name(struct_['name1'], struct_['name2'], struct_['name3'])
|
||||
if struct_['is_last']:
|
||||
break
|
||||
return ''.join(map(lambda x: x[1], sorted(full_name.items()))) or obj_name_
|
||||
|
||||
|
||||
def traverse_folder_tree(directory_bytes_: bytes,
|
||||
name: str,
|
||||
state_: BootSectorState,
|
||||
fat_: FAT,
|
||||
binary_array_: bytes) -> None:
|
||||
os.makedirs(name)
|
||||
|
||||
assert len(directory_bytes_) % FATDefaults.ENTRY_SIZE == 0
|
||||
entries_count_: int = len(directory_bytes_) // FATDefaults.ENTRY_SIZE
|
||||
|
||||
for i in range(entries_count_):
|
||||
obj_address_: int = FATDefaults.ENTRY_SIZE * i
|
||||
try:
|
||||
obj_: dict = Entry.ENTRY_FORMAT_SHORT_NAME.parse(
|
||||
directory_bytes_[obj_address_: obj_address_ + FATDefaults.ENTRY_SIZE])
|
||||
except (construct.core.ConstError, UnicodeDecodeError):
|
||||
args.long_name_support = True
|
||||
continue
|
||||
|
||||
if obj_['DIR_Attr'] == 0: # empty entry
|
||||
continue
|
||||
|
||||
obj_name_: str = get_obj_name(obj_,
|
||||
directory_bytes_,
|
||||
entry_position_=i,
|
||||
lfn_checksum_=lfn_checksum(obj_['DIR_Name'] + obj_['DIR_Name_ext']))
|
||||
if obj_['DIR_Attr'] == Entry.ATTR_ARCHIVE:
|
||||
content_ = b''
|
||||
if obj_['DIR_FileSize'] > 0:
|
||||
content_ = fat_.get_chained_content(cluster_id_=Entry.get_cluster_id(obj_),
|
||||
size=obj_['DIR_FileSize'])
|
||||
with open(os.path.join(name, obj_name_), 'wb') as new_file:
|
||||
new_file.write(content_)
|
||||
elif obj_['DIR_Attr'] == Entry.ATTR_DIRECTORY:
|
||||
# avoid creating symlinks to itself and parent folder
|
||||
if obj_name_ in ('.', '..'):
|
||||
continue
|
||||
child_directory_bytes_ = fat_.get_chained_content(cluster_id_=obj_['DIR_FstClusLO'])
|
||||
traverse_folder_tree(directory_bytes_=child_directory_bytes_,
|
||||
name=os.path.join(name, obj_name_),
|
||||
state_=state_,
|
||||
fat_=fat_,
|
||||
binary_array_=binary_array_)
|
||||
|
||||
|
||||
def remove_wear_levelling_if_exists(fs_: bytes) -> bytes:
|
||||
"""
|
||||
Detection of the wear levelling layer is performed in two steps:
|
||||
1) check if the first sector is a valid boot sector
|
||||
2) check if the size defined in the boot sector is the same as the partition size:
|
||||
- if it is, there is no wear levelling layer
|
||||
- otherwise, we need to remove wl for further processing
|
||||
"""
|
||||
try:
|
||||
boot_sector__ = BootSector()
|
||||
boot_sector__.parse_boot_sector(fs_)
|
||||
if boot_sector__.boot_sector_state.size == len(fs_):
|
||||
return fs_
|
||||
except construct.core.ConstError:
|
||||
pass
|
||||
plain_fs: bytes = remove_wl(fs_)
|
||||
return plain_fs
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
desc = 'Tool for parsing fatfs image and extracting directory structure on host.'
|
||||
argument_parser: argparse.ArgumentParser = argparse.ArgumentParser(description=desc)
|
||||
argument_parser.add_argument('input_image',
|
||||
help='Path to the image that will be parsed and extracted.')
|
||||
argument_parser.add_argument('--long-name-support',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# ensures backward compatibility
|
||||
argument_parser.add_argument('--wear-leveling',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
argument_parser.add_argument('--wl-layer',
|
||||
choices=['detect', 'enabled', 'disabled'],
|
||||
default=None,
|
||||
help="If detection doesn't work correctly, "
|
||||
'you can force analyzer to or not to assume WL.')
|
||||
|
||||
args = argument_parser.parse_args()
|
||||
|
||||
# if wear levelling is detected or user explicitly sets the parameter `--wl_layer enabled`
|
||||
# the partition with wear levelling is transformed to partition without WL for convenient parsing
|
||||
# in some cases the partitions with and without wear levelling can be 100% equivalent
|
||||
# and only user can break this tie by explicitly setting
|
||||
# the parameter --wl-layer to enabled, respectively disabled
|
||||
if args.wear_leveling and args.wl_layer:
|
||||
raise NotImplementedError('Argument --wear-leveling cannot be combined with --wl-layer!')
|
||||
if args.wear_leveling:
|
||||
args.wl_layer = 'enabled'
|
||||
args.wl_layer = args.wl_layer or 'detect'
|
||||
|
||||
fs = read_filesystem(args.input_image)
|
||||
|
||||
# An algorithm for removing wear levelling:
|
||||
# 1. find an remove dummy sector:
|
||||
# a) dummy sector is at the position defined by the number of records in the state sector
|
||||
# b) dummy may not be placed in state nor cfg sectors
|
||||
# c) first (boot) sector position (boot_s_pos) is calculated using value of move count
|
||||
# boot_s_pos = - mc
|
||||
# 2. remove state sectors (trivial)
|
||||
# 3. remove cfg sector (trivial)
|
||||
# 4. valid fs is then old_fs[-mc:] + old_fs[:-mc]
|
||||
if args.wl_layer == 'enabled':
|
||||
fs = remove_wl(fs)
|
||||
elif args.wl_layer != 'disabled':
|
||||
# wear levelling is removed to enable parsing using common algorithm
|
||||
fs = remove_wear_levelling_if_exists(fs)
|
||||
|
||||
boot_sector_ = BootSector()
|
||||
boot_sector_.parse_boot_sector(fs)
|
||||
fat = FAT(boot_sector_.boot_sector_state, init_=False)
|
||||
|
||||
boot_dir_start_ = boot_sector_.boot_sector_state.root_directory_start
|
||||
boot_dir_sectors = boot_sector_.boot_sector_state.root_dir_sectors_cnt
|
||||
full_ = fs[boot_dir_start_: boot_dir_start_ + boot_dir_sectors * boot_sector_.boot_sector_state.sector_size]
|
||||
traverse_folder_tree(full_,
|
||||
boot_sector_.boot_sector_state.volume_label.rstrip(chr(PAD_CHAR)),
|
||||
boot_sector_.boot_sector_state, fat, fs)
|
||||
@@ -0,0 +1,212 @@
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* A Sample Code of User Provided OS Dependent Functions for FatFs */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "ff.h"
|
||||
#include "sdkconfig.h"
|
||||
#ifdef CONFIG_FATFS_ALLOC_PREFER_EXTRAM
|
||||
#include "esp_heap_caps.h"
|
||||
#endif
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Allocate/Free a Memory Block */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
|
||||
unsigned msize /* Number of bytes to allocate */
|
||||
)
|
||||
{
|
||||
#ifdef CONFIG_FATFS_ALLOC_PREFER_EXTRAM
|
||||
return heap_caps_malloc_prefer(msize, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM,
|
||||
MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
|
||||
#else
|
||||
return malloc(msize);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void ff_memfree (
|
||||
void* mblock /* Pointer to the memory block to free (no effect if null) */
|
||||
)
|
||||
{
|
||||
free(mblock); /* Free the memory block with POSIX API */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if FF_FS_REENTRANT /* Mutal exclusion */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Definitions of Mutex */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
#define OS_TYPE 3 /* 0:Win32, 1:uITRON4.0, 2:uC/OS-II, 3:FreeRTOS, 4:CMSIS-RTOS */
|
||||
|
||||
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
#include <windows.h>
|
||||
static HANDLE Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
#include "itron.h"
|
||||
#include "kernel.h"
|
||||
static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
|
||||
|
||||
#elif OS_TYPE == 2 /* uc/OS-II */
|
||||
#include "includes.h"
|
||||
static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
static SemaphoreHandle_t Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
#include "cmsis_os.h"
|
||||
static osMutexId Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Create a Mutex */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called in f_mount function to create a new mutex
|
||||
/ or semaphore for the volume. When a 0 is returned, the f_mount function
|
||||
/ fails with FR_INT_ERR.
|
||||
*/
|
||||
|
||||
int ff_mutex_create ( /* Returns 1:Function succeeded or 0:Could not create the mutex */
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
Mutex[vol] = CreateMutex(NULL, FALSE, NULL);
|
||||
return (int)(Mutex[vol] != INVALID_HANDLE_VALUE);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
T_CMTX cmtx = {TA_TPRI,1};
|
||||
|
||||
Mutex[vol] = acre_mtx(&cmtx);
|
||||
return (int)(Mutex[vol] > 0);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OS_ERR err;
|
||||
|
||||
Mutex[vol] = OSMutexCreate(0, &err);
|
||||
return (int)(err == OS_NO_ERR);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
Mutex[vol] = xSemaphoreCreateMutex();
|
||||
return (int)(Mutex[vol] != NULL);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
osMutexDef(cmsis_os_mutex);
|
||||
|
||||
Mutex[vol] = osMutexCreate(osMutex(cmsis_os_mutex));
|
||||
return (int)(Mutex[vol] != NULL);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Delete a Mutex */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called in f_mount function to delete a mutex or
|
||||
/ semaphore of the volume created with ff_mutex_create function.
|
||||
*/
|
||||
|
||||
void ff_mutex_delete ( /* Returns 1:Function succeeded or 0:Could not delete due to an error */
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
CloseHandle(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
del_mtx(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OS_ERR err;
|
||||
|
||||
OSMutexDel(Mutex[vol], OS_DEL_ALWAYS, &err);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
vSemaphoreDelete(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
osMutexDelete(Mutex[vol]);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Request a Grant to Access the Volume */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called on enter file functions to lock the volume.
|
||||
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
|
||||
*/
|
||||
|
||||
int ff_mutex_take ( /* Returns 1:Succeeded or 0:Timeout */
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
return (int)(WaitForSingleObject(Mutex[vol], FF_FS_TIMEOUT) == WAIT_OBJECT_0);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
return (int)(tloc_mtx(Mutex[vol], FF_FS_TIMEOUT) == E_OK);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OS_ERR err;
|
||||
|
||||
OSMutexPend(Mutex[vol], FF_FS_TIMEOUT, &err));
|
||||
return (int)(err == OS_NO_ERR);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
return (int)(xSemaphoreTake(Mutex[vol], FF_FS_TIMEOUT) == pdTRUE);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
return (int)(osMutexWait(Mutex[vol], FF_FS_TIMEOUT) == osOK);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Release a Grant to Access the Volume */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called on leave file functions to unlock the volume.
|
||||
*/
|
||||
|
||||
void ff_mutex_give (
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
ReleaseMutex(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
unl_mtx(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OSMutexPost(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
xSemaphoreGive(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
osMutexRelease(Mutex[vol]);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* FF_FS_REENTRANT */
|
||||
@@ -0,0 +1,45 @@
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* OS Dependent Functions for FatFs */
|
||||
/* (C)ChaN, 2018 */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#include "ff.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
/* This is the implementation for host-side testing on Linux.
|
||||
* Host-side tests are single threaded, so lock functionality isn't needed.
|
||||
*/
|
||||
|
||||
void* ff_memalloc(UINT msize)
|
||||
{
|
||||
return malloc(msize);
|
||||
}
|
||||
|
||||
void ff_memfree(void* mblock)
|
||||
{
|
||||
free(mblock);
|
||||
}
|
||||
|
||||
static int* Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||
|
||||
/* 1:Function succeeded, 0:Could not create the mutex */
|
||||
int ff_mutex_create(int vol)
|
||||
{
|
||||
Mutex[vol] = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ff_mutex_delete(int vol)
|
||||
{
|
||||
}
|
||||
|
||||
/* 1:Function succeeded, 0:Could not acquire lock */
|
||||
int ff_mutex_take(int vol)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ff_mutex_give(int vol)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
# fatfs_create_partition_image
|
||||
#
|
||||
# Create a fatfs image of the specified directory on the host during build and optionally
|
||||
# have the created image flashed using `idf.py flash`
|
||||
function(fatfs_create_partition_image partition base_dir)
|
||||
set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME)
|
||||
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
|
||||
|
||||
|
||||
idf_build_get_property(idf_path IDF_PATH)
|
||||
idf_build_get_property(python PYTHON)
|
||||
|
||||
if(arg_WL_INIT)
|
||||
set(fatfsgen_py ${python} ${idf_path}/components/fatfs/wl_fatfsgen.py)
|
||||
else()
|
||||
set(fatfsgen_py ${python} ${idf_path}/components/fatfs/fatfsgen.py)
|
||||
endif()
|
||||
|
||||
if(arg_PRESERVE_TIME)
|
||||
set(default_datetime_option)
|
||||
else()
|
||||
set(default_datetime_option --use_default_datetime)
|
||||
endif()
|
||||
|
||||
if("${CONFIG_FATFS_SECTOR_512}")
|
||||
set(fatfs_sector_size 512)
|
||||
elseif("${CONFIG_FATFS_SECTOR_1024}")
|
||||
set(fatfs_sector_size 1024)
|
||||
elseif("${CONFIG_FATFS_SECTOR_2048}")
|
||||
set(fatfs_sector_size 2048)
|
||||
else()
|
||||
set(fatfs_sector_size 4096)
|
||||
endif()
|
||||
|
||||
if("${CONFIG_FATFS_LFN_NONE}")
|
||||
set(fatfs_long_names_option)
|
||||
elseif("${CONFIG_FATFS_LFN_HEAP}")
|
||||
set(fatfs_long_names_option --long_name_support)
|
||||
elseif("${CONFIG_FATFS_LFN_STACK}")
|
||||
set(fatfs_long_names_option --long_name_support)
|
||||
endif()
|
||||
|
||||
get_filename_component(base_dir_full_path ${base_dir} ABSOLUTE)
|
||||
partition_table_get_partition_info(size "--partition-name ${partition}" "size")
|
||||
partition_table_get_partition_info(offset "--partition-name ${partition}" "offset")
|
||||
|
||||
if("${size}" AND "${offset}")
|
||||
set(image_file ${CMAKE_BINARY_DIR}/${partition}.bin)
|
||||
# Execute FATFS image generation; this always executes as there is no way to specify for CMake to watch for
|
||||
# contents of the base dir changing.
|
||||
add_custom_target(fatfs_${partition}_bin ALL
|
||||
COMMAND ${fatfsgen_py} ${base_dir_full_path}
|
||||
${fatfs_long_names_option}
|
||||
${default_datetime_option}
|
||||
--partition_size ${size}
|
||||
--output_file ${image_file}
|
||||
--sector_size "${fatfs_sector_size}"
|
||||
)
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
|
||||
ADDITIONAL_CLEAN_FILES
|
||||
${image_file})
|
||||
|
||||
idf_component_get_property(main_args esptool_py FLASH_ARGS)
|
||||
idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS)
|
||||
# Last (optional) parameter is the encryption for the target. In our
|
||||
# case, fatfs is not encrypt so pass FALSE to the function.
|
||||
esptool_py_flash_target(${partition}-flash "${main_args}" "${sub_args}" ALWAYS_PLAINTEXT)
|
||||
esptool_py_flash_to_partition(${partition}-flash "${partition}" "${image_file}")
|
||||
|
||||
add_dependencies(${partition}-flash fatfs_${partition}_bin)
|
||||
if(arg_FLASH_IN_PROJECT)
|
||||
esptool_py_flash_to_partition(flash "${partition}" "${image_file}")
|
||||
add_dependencies(flash fatfs_${partition}_bin)
|
||||
endif()
|
||||
else()
|
||||
set(message "Failed to create FATFS image for partition '${partition}'. "
|
||||
"Check project configuration if using the correct partition table file.")
|
||||
fail_at_build_time(fatfs_${partition}_bin "${message}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
function(fatfs_create_rawflash_image partition base_dir)
|
||||
set(options FLASH_IN_PROJECT PRESERVE_TIME)
|
||||
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
|
||||
|
||||
if(arg_FLASH_IN_PROJECT)
|
||||
if(arg_PRESERVE_TIME)
|
||||
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT PRESERVE_TIME)
|
||||
else()
|
||||
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT)
|
||||
endif()
|
||||
else()
|
||||
if(arg_PRESERVE_TIME)
|
||||
fatfs_create_partition_image(${partition} ${base_dir} PRESERVE_TIME)
|
||||
else()
|
||||
fatfs_create_partition_image(${partition} ${base_dir})
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(fatfs_create_spiflash_image partition base_dir)
|
||||
set(options FLASH_IN_PROJECT PRESERVE_TIME)
|
||||
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
|
||||
|
||||
if(arg_FLASH_IN_PROJECT)
|
||||
if(arg_PRESERVE_TIME)
|
||||
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT PRESERVE_TIME)
|
||||
else()
|
||||
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT)
|
||||
endif()
|
||||
else()
|
||||
if(arg_PRESERVE_TIME)
|
||||
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT PRESERVE_TIME)
|
||||
else()
|
||||
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,368 @@
|
||||
----------------------------------------------------------------------------
|
||||
Revision history of FatFs module
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
R0.00 (February 26, 2006)
|
||||
|
||||
Prototype.
|
||||
|
||||
|
||||
|
||||
R0.01 (April 29, 2006)
|
||||
|
||||
The first release.
|
||||
|
||||
|
||||
|
||||
R0.02 (June 01, 2006)
|
||||
|
||||
Added FAT12 support.
|
||||
Removed unbuffered mode.
|
||||
Fixed a problem on small (<32M) partition.
|
||||
|
||||
|
||||
|
||||
R0.02a (June 10, 2006)
|
||||
|
||||
Added a configuration option (_FS_MINIMUM).
|
||||
|
||||
|
||||
|
||||
R0.03 (September 22, 2006)
|
||||
|
||||
Added f_rename().
|
||||
Changed option _FS_MINIMUM to _FS_MINIMIZE.
|
||||
|
||||
|
||||
|
||||
R0.03a (December 11, 2006)
|
||||
|
||||
Improved cluster scan algorithm to write files fast.
|
||||
Fixed f_mkdir() creates incorrect directory on FAT32.
|
||||
|
||||
|
||||
|
||||
R0.04 (February 04, 2007)
|
||||
|
||||
Added f_mkfs().
|
||||
Supported multiple drive system.
|
||||
Changed some interfaces for multiple drive system.
|
||||
Changed f_mountdrv() to f_mount().
|
||||
|
||||
|
||||
|
||||
R0.04a (April 01, 2007)
|
||||
|
||||
Supported multiple partitions on a physical drive.
|
||||
Added a capability of extending file size to f_lseek().
|
||||
Added minimization level 3.
|
||||
Fixed an endian sensitive code in f_mkfs().
|
||||
|
||||
|
||||
|
||||
R0.04b (May 05, 2007)
|
||||
|
||||
Added a configuration option _USE_NTFLAG.
|
||||
Added FSINFO support.
|
||||
Fixed DBCS name can result FR_INVALID_NAME.
|
||||
Fixed short seek (<= csize) collapses the file object.
|
||||
|
||||
|
||||
|
||||
R0.05 (August 25, 2007)
|
||||
|
||||
Changed arguments of f_read(), f_write() and f_mkfs().
|
||||
Fixed f_mkfs() on FAT32 creates incorrect FSINFO.
|
||||
Fixed f_mkdir() on FAT32 creates incorrect directory.
|
||||
|
||||
|
||||
|
||||
R0.05a (February 03, 2008)
|
||||
|
||||
Added f_truncate() and f_utime().
|
||||
Fixed off by one error at FAT sub-type determination.
|
||||
Fixed btr in f_read() can be mistruncated.
|
||||
Fixed cached sector is not flushed when create and close without write.
|
||||
|
||||
|
||||
|
||||
R0.06 (April 01, 2008)
|
||||
|
||||
Added fputc(), fputs(), fprintf() and fgets().
|
||||
Improved performance of f_lseek() on moving to the same or following cluster.
|
||||
|
||||
|
||||
|
||||
R0.07 (April 01, 2009)
|
||||
|
||||
Merged Tiny-FatFs as a configuration option. (_FS_TINY)
|
||||
Added long file name feature. (_USE_LFN)
|
||||
Added multiple code page feature. (_CODE_PAGE)
|
||||
Added re-entrancy for multitask operation. (_FS_REENTRANT)
|
||||
Added auto cluster size selection to f_mkfs().
|
||||
Added rewind option to f_readdir().
|
||||
Changed result code of critical errors.
|
||||
Renamed string functions to avoid name collision.
|
||||
|
||||
|
||||
|
||||
R0.07a (April 14, 2009)
|
||||
|
||||
Septemberarated out OS dependent code on reentrant cfg.
|
||||
Added multiple sector size feature.
|
||||
|
||||
|
||||
|
||||
R0.07c (June 21, 2009)
|
||||
|
||||
Fixed f_unlink() can return FR_OK on error.
|
||||
Fixed wrong cache control in f_lseek().
|
||||
Added relative path feature.
|
||||
Added f_chdir() and f_chdrive().
|
||||
Added proper case conversion to extended character.
|
||||
|
||||
|
||||
|
||||
R0.07e (November 03, 2009)
|
||||
|
||||
Septemberarated out configuration options from ff.h to ffconf.h.
|
||||
Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH.
|
||||
Fixed name matching error on the 13 character boundary.
|
||||
Added a configuration option, _LFN_UNICODE.
|
||||
Changed f_readdir() to return the SFN with always upper case on non-LFN cfg.
|
||||
|
||||
|
||||
|
||||
R0.08 (May 15, 2010)
|
||||
|
||||
Added a memory configuration option. (_USE_LFN = 3)
|
||||
Added file lock feature. (_FS_SHARE)
|
||||
Added fast seek feature. (_USE_FASTSEEK)
|
||||
Changed some types on the API, XCHAR->TCHAR.
|
||||
Changed .fname in the FILINFO structure on Unicode cfg.
|
||||
String functions support UTF-8 encoding files on Unicode cfg.
|
||||
|
||||
|
||||
|
||||
R0.08a (August 16, 2010)
|
||||
|
||||
Added f_getcwd(). (_FS_RPATH = 2)
|
||||
Added sector erase feature. (_USE_ERASE)
|
||||
Moved file lock semaphore table from fs object to the bss.
|
||||
Fixed f_mkfs() creates wrong FAT32 volume.
|
||||
|
||||
|
||||
|
||||
R0.08b (January 15, 2011)
|
||||
|
||||
Fast seek feature is also applied to f_read() and f_write().
|
||||
f_lseek() reports required table size on creating CLMP.
|
||||
Extended format syntax of f_printf().
|
||||
Ignores duplicated directory separators in given path name.
|
||||
|
||||
|
||||
|
||||
R0.09 (September 06, 2011)
|
||||
|
||||
f_mkfs() supports multiple partition to complete the multiple partition feature.
|
||||
Added f_fdisk().
|
||||
|
||||
|
||||
|
||||
R0.09a (August 27, 2012)
|
||||
|
||||
Changed f_open() and f_opendir() reject null object pointer to avoid crash.
|
||||
Changed option name _FS_SHARE to _FS_LOCK.
|
||||
Fixed assertion failure due to OS/2 EA on FAT12/16 volume.
|
||||
|
||||
|
||||
|
||||
R0.09b (January 24, 2013)
|
||||
|
||||
Added f_setlabel() and f_getlabel().
|
||||
|
||||
|
||||
|
||||
R0.10 (October 02, 2013)
|
||||
|
||||
Added selection of character encoding on the file. (_STRF_ENCODE)
|
||||
Added f_closedir().
|
||||
Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO)
|
||||
Added forced mount feature with changes of f_mount().
|
||||
Improved behavior of volume auto detection.
|
||||
Improved write throughput of f_puts() and f_printf().
|
||||
Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write().
|
||||
Fixed f_write() can be truncated when the file size is close to 4GB.
|
||||
Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error.
|
||||
|
||||
|
||||
|
||||
R0.10a (January 15, 2014)
|
||||
|
||||
Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID)
|
||||
Added a configuration option of minimum sector size. (_MIN_SS)
|
||||
2nd argument of f_rename() can have a drive number and it will be ignored.
|
||||
Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10)
|
||||
Fixed f_close() invalidates the file object without volume lock.
|
||||
Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10)
|
||||
Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07)
|
||||
|
||||
|
||||
|
||||
R0.10b (May 19, 2014)
|
||||
|
||||
Fixed a hard error in the disk I/O layer can collapse the directory entry.
|
||||
Fixed LFN entry is not deleted when delete/rename an object with lossy converted SFN. (appeared at R0.07)
|
||||
|
||||
|
||||
|
||||
R0.10c (November 09, 2014)
|
||||
|
||||
Added a configuration option for the platforms without RTC. (_FS_NORTC)
|
||||
Changed option name _USE_ERASE to _USE_TRIM.
|
||||
Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b)
|
||||
Fixed a potential problem of FAT access that can appear on disk error.
|
||||
Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08)
|
||||
|
||||
|
||||
|
||||
R0.11 (February 09, 2015)
|
||||
|
||||
Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND)
|
||||
Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c)
|
||||
Fixed _FS_NORTC option does not work properly. (appeared at R0.10c)
|
||||
|
||||
|
||||
|
||||
R0.11a (September 05, 2015)
|
||||
|
||||
Fixed wrong media change can lead a deadlock at thread-safe configuration.
|
||||
Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE)
|
||||
Removed some code pages actually not exist on the standard systems. (_CODE_PAGE)
|
||||
Fixed errors in the case conversion teble of code page 437 and 850 (ff.c).
|
||||
Fixed errors in the case conversion teble of Unicode (cc*.c).
|
||||
|
||||
|
||||
|
||||
R0.12 (April 12, 2016)
|
||||
|
||||
Added support for exFAT file system. (_FS_EXFAT)
|
||||
Added f_expand(). (_USE_EXPAND)
|
||||
Changed some members in FINFO structure and behavior of f_readdir().
|
||||
Added an option _USE_CHMOD.
|
||||
Removed an option _WORD_ACCESS.
|
||||
Fixed errors in the case conversion table of Unicode (cc*.c).
|
||||
|
||||
|
||||
|
||||
R0.12a (July 10, 2016)
|
||||
|
||||
Added support for creating exFAT volume with some changes of f_mkfs().
|
||||
Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed.
|
||||
f_forward() is available regardless of _FS_TINY.
|
||||
Fixed f_mkfs() creates wrong volume. (appeared at R0.12)
|
||||
Fixed wrong memory read in create_name(). (appeared at R0.12)
|
||||
Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD.
|
||||
|
||||
|
||||
|
||||
R0.12b (September 04, 2016)
|
||||
|
||||
Made f_rename() be able to rename objects with the same name but case.
|
||||
Fixed an error in the case conversion teble of code page 866. (ff.c)
|
||||
Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12)
|
||||
Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12)
|
||||
Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12)
|
||||
Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12)
|
||||
Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12)
|
||||
Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12)
|
||||
|
||||
|
||||
|
||||
R0.12c (March 04, 2017)
|
||||
|
||||
Improved write throughput at the fragmented file on the exFAT volume.
|
||||
Made memory usage for exFAT be able to be reduced as decreasing _MAX_LFN.
|
||||
Fixed successive f_getfree() can return wrong count on the FAT12/16 volume. (appeared at R0.12)
|
||||
Fixed configuration option _VOLUMES cannot be set 10. (appeared at R0.10c)
|
||||
|
||||
|
||||
|
||||
R0.13 (May 21, 2017)
|
||||
|
||||
Changed heading character of configuration keywords "_" to "FF_".
|
||||
Removed ASCII-only configuration, FF_CODE_PAGE = 1. Use FF_CODE_PAGE = 437 instead.
|
||||
Added f_setcp(), run-time code page configuration. (FF_CODE_PAGE = 0)
|
||||
Improved cluster allocation time on stretch a deep buried cluster chain.
|
||||
Improved processing time of f_mkdir() with large cluster size by using FF_USE_LFN = 3.
|
||||
Improved NoFatChain flag of the fragmented file to be set after it is truncated and got contiguous.
|
||||
Fixed archive attribute is left not set when a file on the exFAT volume is renamed. (appeared at R0.12)
|
||||
Fixed exFAT FAT entry can be collapsed when write or lseek operation to the existing file is done. (appeared at R0.12c)
|
||||
Fixed creating a file can fail when a new cluster allocation to the exFAT directory occures. (appeared at R0.12c)
|
||||
|
||||
|
||||
|
||||
R0.13a (October 14, 2017)
|
||||
|
||||
Added support for UTF-8 encoding on the API. (FF_LFN_UNICODE = 2)
|
||||
Added options for file name output buffer. (FF_LFN_BUF, FF_SFN_BUF).
|
||||
Added dynamic memory allocation option for working buffer of f_mkfs() and f_fdisk().
|
||||
Fixed f_fdisk() and f_mkfs() create the partition table with wrong CHS parameters. (appeared at R0.09)
|
||||
Fixed f_unlink() can cause lost clusters at fragmented file on the exFAT volume. (appeared at R0.12c)
|
||||
Fixed f_setlabel() rejects some valid characters for exFAT volume. (appeared at R0.12)
|
||||
|
||||
|
||||
|
||||
R0.13b (April 07, 2018)
|
||||
|
||||
Added support for UTF-32 encoding on the API. (FF_LFN_UNICODE = 3)
|
||||
Added support for Unix style volume ID. (FF_STR_VOLUME_ID = 2)
|
||||
Fixed accesing any object on the exFAT root directory beyond the cluster boundary can fail. (appeared at R0.12c)
|
||||
Fixed f_setlabel() does not reject some invalid characters. (appeared at R0.09b)
|
||||
|
||||
|
||||
|
||||
R0.13c (October 14, 2018)
|
||||
Supported stdint.h for C99 and later. (integer.h was included in ff.h)
|
||||
Fixed reading a directory gets infinite loop when the last directory entry is not empty. (appeared at R0.12)
|
||||
Fixed creating a sub-directory in the fragmented sub-directory on the exFAT volume collapses FAT chain of the parent directory. (appeared at R0.12)
|
||||
Fixed f_getcwd() cause output buffer overrun when the buffer has a valid drive number. (appeared at R0.13b)
|
||||
|
||||
|
||||
|
||||
R0.14 (October 14, 2019)
|
||||
Added support for 64-bit LBA and GUID partition table (FF_LBA64 = 1)
|
||||
Changed some API functions, f_mkfs() and f_fdisk().
|
||||
Fixed f_open() function cannot find the file with file name in length of FF_MAX_LFN characters.
|
||||
Fixed f_readdir() function cannot retrieve long file names in length of FF_MAX_LFN - 1 characters.
|
||||
Fixed f_readdir() function returns file names with wrong case conversion. (appeared at R0.12)
|
||||
Fixed f_mkfs() function can fail to create exFAT volume in the second partition. (appeared at R0.12)
|
||||
|
||||
|
||||
R0.14a (December 5, 2020)
|
||||
Limited number of recursive calls in f_findnext().
|
||||
Fixed old floppy disks formatted with MS-DOS 2.x and 3.x cannot be mounted.
|
||||
Fixed some compiler warnings.
|
||||
|
||||
|
||||
|
||||
R0.14b (April 17, 2021)
|
||||
Made FatFs uses standard library <string.h> for copy, compare and search instead of built-in string functions.
|
||||
Added support for long long integer and floating point to f_printf(). (FF_STRF_LLI and FF_STRF_FP)
|
||||
Made path name parser ignore the terminating separator to allow "dir/".
|
||||
Improved the compatibility in Unix style path name feature.
|
||||
Fixed the file gets dead-locked when f_open() failed with some conditions. (appeared at R0.12a)
|
||||
Fixed f_mkfs() can create wrong exFAT volume due to a timing dependent error. (appeared at R0.12)
|
||||
Fixed code page 855 cannot be set by f_setcp().
|
||||
Fixed some compiler warnings.
|
||||
|
||||
|
||||
|
||||
R0.15 (November 6, 2022)
|
||||
Changed user provided synchronization functions in order to completely eliminate the platform dependency from FatFs code.
|
||||
FF_SYNC_t is removed from the configuration options.
|
||||
Fixed a potential error in f_mount when FF_FS_REENTRANT.
|
||||
Fixed file lock control FF_FS_LOCK is not mutal excluded when FF_FS_REENTRANT && FF_VOLUMES > 1 is true.
|
||||
Fixed f_mkfs() creates broken exFAT volume when the size of volume is >= 2^32 sectors.
|
||||
Fixed string functions cannot write the unicode characters not in BMP when FF_LFN_UNICODE == 2 (UTF-8).
|
||||
Fixed a compatibility issue in identification of GPT header.
|
||||
@@ -0,0 +1,20 @@
|
||||
FatFs Module Source Files R0.15
|
||||
|
||||
|
||||
FILES
|
||||
|
||||
00readme.txt This file.
|
||||
00history.txt Revision history.
|
||||
ff.c FatFs module.
|
||||
ffconf.h Configuration file of FatFs module.
|
||||
ff.h Common include file for FatFs and application module.
|
||||
diskio.h Common include file for FatFs and disk I/O module.
|
||||
diskio.c An example of glue function to attach existing disk I/O module to FatFs.
|
||||
ffunicode.c Optional Unicode utility functions.
|
||||
ffsystem.c An example of optional O/S related functions.
|
||||
|
||||
|
||||
Low level disk I/O module is not included in this archive because the FatFs
|
||||
module is only a generic file system layer and it does not depend on any specific
|
||||
storage device. You need to provide a low level disk I/O module written to
|
||||
control the storage device that attached to the target system.
|
||||
@@ -0,0 +1,228 @@
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* If a working storage control module is available, it should be */
|
||||
/* attached to the FatFs via a glue function rather than modifying it. */
|
||||
/* This is an example of glue functions to attach various exsisting */
|
||||
/* storage control modules to the FatFs module with a defined API. */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
#include "ff.h" /* Obtains integer types */
|
||||
#include "diskio.h" /* Declarations of disk functions */
|
||||
|
||||
/* Definitions of physical drive number for each drive */
|
||||
#define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */
|
||||
#define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
|
||||
#define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Get Drive Status */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DSTATUS disk_status (
|
||||
BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
)
|
||||
{
|
||||
DSTATUS stat;
|
||||
int result;
|
||||
|
||||
switch (pdrv) {
|
||||
case DEV_RAM :
|
||||
result = RAM_disk_status();
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return stat;
|
||||
|
||||
case DEV_MMC :
|
||||
result = MMC_disk_status();
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return stat;
|
||||
|
||||
case DEV_USB :
|
||||
result = USB_disk_status();
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return stat;
|
||||
}
|
||||
return STA_NOINIT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Inidialize a Drive */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DSTATUS disk_initialize (
|
||||
BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
)
|
||||
{
|
||||
DSTATUS stat;
|
||||
int result;
|
||||
|
||||
switch (pdrv) {
|
||||
case DEV_RAM :
|
||||
result = RAM_disk_initialize();
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return stat;
|
||||
|
||||
case DEV_MMC :
|
||||
result = MMC_disk_initialize();
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return stat;
|
||||
|
||||
case DEV_USB :
|
||||
result = USB_disk_initialize();
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return stat;
|
||||
}
|
||||
return STA_NOINIT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Read Sector(s) */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DRESULT disk_read (
|
||||
BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
BYTE *buff, /* Data buffer to store read data */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to read */
|
||||
)
|
||||
{
|
||||
DRESULT res;
|
||||
int result;
|
||||
|
||||
switch (pdrv) {
|
||||
case DEV_RAM :
|
||||
// translate the arguments here
|
||||
|
||||
result = RAM_disk_read(buff, sector, count);
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return res;
|
||||
|
||||
case DEV_MMC :
|
||||
// translate the arguments here
|
||||
|
||||
result = MMC_disk_read(buff, sector, count);
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return res;
|
||||
|
||||
case DEV_USB :
|
||||
// translate the arguments here
|
||||
|
||||
result = USB_disk_read(buff, sector, count);
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return RES_PARERR;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Write Sector(s) */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
#if FF_FS_READONLY == 0
|
||||
|
||||
DRESULT disk_write (
|
||||
BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
const BYTE *buff, /* Data to be written */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to write */
|
||||
)
|
||||
{
|
||||
DRESULT res;
|
||||
int result;
|
||||
|
||||
switch (pdrv) {
|
||||
case DEV_RAM :
|
||||
// translate the arguments here
|
||||
|
||||
result = RAM_disk_write(buff, sector, count);
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return res;
|
||||
|
||||
case DEV_MMC :
|
||||
// translate the arguments here
|
||||
|
||||
result = MMC_disk_write(buff, sector, count);
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return res;
|
||||
|
||||
case DEV_USB :
|
||||
// translate the arguments here
|
||||
|
||||
result = USB_disk_write(buff, sector, count);
|
||||
|
||||
// translate the reslut code here
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return RES_PARERR;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Miscellaneous Functions */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DRESULT disk_ioctl (
|
||||
BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||
BYTE cmd, /* Control code */
|
||||
void *buff /* Buffer to send/receive control data */
|
||||
)
|
||||
{
|
||||
DRESULT res;
|
||||
int result;
|
||||
|
||||
switch (pdrv) {
|
||||
case DEV_RAM :
|
||||
|
||||
// Process of the command for the RAM drive
|
||||
|
||||
return res;
|
||||
|
||||
case DEV_MMC :
|
||||
|
||||
// Process of the command for the MMC/SD card
|
||||
|
||||
return res;
|
||||
|
||||
case DEV_USB :
|
||||
|
||||
// Process of the command the USB drive
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return RES_PARERR;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*-----------------------------------------------------------------------/
|
||||
/ Low level disk interface modlue include file (C)ChaN, 2019 /
|
||||
/-----------------------------------------------------------------------*/
|
||||
|
||||
#ifndef _DISKIO_DEFINED
|
||||
#define _DISKIO_DEFINED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
/* Status of Disk Functions */
|
||||
typedef BYTE DSTATUS;
|
||||
|
||||
/* Results of Disk Functions */
|
||||
typedef enum {
|
||||
RES_OK = 0, /* 0: Successful */
|
||||
RES_ERROR, /* 1: R/W Error */
|
||||
RES_WRPRT, /* 2: Write Protected */
|
||||
RES_NOTRDY, /* 3: Not Ready */
|
||||
RES_PARERR /* 4: Invalid Parameter */
|
||||
} DRESULT;
|
||||
|
||||
|
||||
/*---------------------------------------*/
|
||||
/* Prototypes for disk control functions */
|
||||
|
||||
|
||||
DSTATUS disk_initialize (BYTE pdrv);
|
||||
DSTATUS disk_status (BYTE pdrv);
|
||||
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
|
||||
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
|
||||
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
|
||||
|
||||
|
||||
/* Disk Status Bits (DSTATUS) */
|
||||
|
||||
#define STA_NOINIT 0x01 /* Drive not initialized */
|
||||
#define STA_NODISK 0x02 /* No medium in the drive */
|
||||
#define STA_PROTECT 0x04 /* Write protected */
|
||||
|
||||
|
||||
/* Command code for disk_ioctrl fucntion */
|
||||
|
||||
/* Generic command (Used by FatFs) */
|
||||
#define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */
|
||||
#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */
|
||||
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */
|
||||
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */
|
||||
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */
|
||||
|
||||
/* Generic command (Not used by FatFs) */
|
||||
#define CTRL_POWER 5 /* Get/Set power status */
|
||||
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
|
||||
#define CTRL_EJECT 7 /* Eject media */
|
||||
#define CTRL_FORMAT 8 /* Create physical format on the media */
|
||||
|
||||
/* MMC/SDC specific ioctl command */
|
||||
#define MMC_GET_TYPE 10 /* Get card type */
|
||||
#define MMC_GET_CSD 11 /* Get CSD */
|
||||
#define MMC_GET_CID 12 /* Get CID */
|
||||
#define MMC_GET_OCR 13 /* Get OCR */
|
||||
#define MMC_GET_SDSTAT 14 /* Get SD status */
|
||||
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
|
||||
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
|
||||
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
|
||||
|
||||
/* ATA/CF specific ioctl command */
|
||||
#define ATA_GET_REV 20 /* Get F/W revision */
|
||||
#define ATA_GET_MODEL 21 /* Get model name */
|
||||
#define ATA_GET_SN 22 /* Get serial number */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+7085
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,429 @@
|
||||
/*----------------------------------------------------------------------------/
|
||||
/ FatFs - Generic FAT Filesystem module R0.15 /
|
||||
/-----------------------------------------------------------------------------/
|
||||
/
|
||||
/ Copyright (C) 2022, ChaN, all right reserved.
|
||||
/
|
||||
/ FatFs module is an open source software. Redistribution and use of FatFs in
|
||||
/ source and binary forms, with or without modification, are permitted provided
|
||||
/ that the following condition is met:
|
||||
|
||||
/ 1. Redistributions of source code must retain the above copyright notice,
|
||||
/ this condition and the following disclaimer.
|
||||
/
|
||||
/ This software is provided by the copyright holder and contributors "AS IS"
|
||||
/ and any warranties related to this software are DISCLAIMED.
|
||||
/ The copyright owner or contributors be NOT LIABLE for any damages caused
|
||||
/ by use of this software.
|
||||
/
|
||||
/----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#ifndef FF_DEFINED
|
||||
#define FF_DEFINED 80286 /* Revision ID */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ffconf.h" /* FatFs configuration options */
|
||||
|
||||
#if FF_DEFINED != FFCONF_DEF
|
||||
#error Wrong configuration file (ffconf.h).
|
||||
#endif
|
||||
|
||||
|
||||
/* Integer types used for FatFs API */
|
||||
|
||||
#if defined(_WIN32) /* Windows VC++ (for development only) */
|
||||
#define FF_INTDEF 2
|
||||
#include <windows.h>
|
||||
typedef unsigned __int64 QWORD;
|
||||
#include <float.h>
|
||||
#define isnan(v) _isnan(v)
|
||||
#define isinf(v) (!_finite(v))
|
||||
|
||||
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
|
||||
#define FF_INTDEF 2
|
||||
#include <stdint.h>
|
||||
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||
typedef uint16_t WORD; /* 16-bit unsigned integer */
|
||||
typedef uint32_t DWORD; /* 32-bit unsigned integer */
|
||||
typedef uint64_t QWORD; /* 64-bit unsigned integer */
|
||||
typedef WORD WCHAR; /* UTF-16 character type */
|
||||
|
||||
#else /* Earlier than C99 */
|
||||
#define FF_INTDEF 1
|
||||
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||
typedef unsigned short WORD; /* 16-bit unsigned integer */
|
||||
typedef unsigned long DWORD; /* 32-bit unsigned integer */
|
||||
typedef WORD WCHAR; /* UTF-16 character type */
|
||||
#endif
|
||||
|
||||
|
||||
/* Type of file size and LBA variables */
|
||||
|
||||
#if FF_FS_EXFAT
|
||||
#if FF_INTDEF != 2
|
||||
#error exFAT feature wants C99 or later
|
||||
#endif
|
||||
typedef QWORD FSIZE_t;
|
||||
#if FF_LBA64
|
||||
typedef QWORD LBA_t;
|
||||
#else
|
||||
typedef DWORD LBA_t;
|
||||
#endif
|
||||
#else
|
||||
#if FF_LBA64
|
||||
#error exFAT needs to be enabled when enable 64-bit LBA
|
||||
#endif
|
||||
typedef DWORD FSIZE_t;
|
||||
typedef DWORD LBA_t;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* Type of path name strings on FatFs API (TCHAR) */
|
||||
|
||||
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
|
||||
typedef WCHAR TCHAR;
|
||||
#define _T(x) L ## x
|
||||
#define _TEXT(x) L ## x
|
||||
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
|
||||
typedef char TCHAR;
|
||||
#define _T(x) u8 ## x
|
||||
#define _TEXT(x) u8 ## x
|
||||
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
|
||||
typedef DWORD TCHAR;
|
||||
#define _T(x) U ## x
|
||||
#define _TEXT(x) U ## x
|
||||
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
|
||||
#error Wrong FF_LFN_UNICODE setting
|
||||
#else /* ANSI/OEM code in SBCS/DBCS */
|
||||
typedef char TCHAR;
|
||||
#define _T(x) x
|
||||
#define _TEXT(x) x
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* Definitions of volume management */
|
||||
|
||||
#if FF_MULTI_PARTITION /* Multiple partition configuration */
|
||||
typedef struct {
|
||||
BYTE pd; /* Physical drive number */
|
||||
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
|
||||
} PARTITION;
|
||||
extern const PARTITION VolToPart[]; /* Volume - Partition mapping table */
|
||||
#endif
|
||||
|
||||
#if FF_STR_VOLUME_ID
|
||||
#ifndef FF_VOLUME_STRS
|
||||
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* Filesystem object structure (FATFS) */
|
||||
|
||||
typedef struct {
|
||||
BYTE fs_type; /* Filesystem type (0:not mounted) */
|
||||
BYTE pdrv; /* Volume hosting physical drive */
|
||||
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
|
||||
BYTE n_fats; /* Number of FATs (1 or 2) */
|
||||
BYTE wflag; /* win[] status (b0:dirty) */
|
||||
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
|
||||
WORD id; /* Volume mount ID */
|
||||
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
|
||||
WORD csize; /* Cluster size [sectors] */
|
||||
#if FF_MAX_SS != FF_MIN_SS
|
||||
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
|
||||
#endif
|
||||
#if FF_USE_LFN
|
||||
WCHAR* lfnbuf; /* LFN working buffer */
|
||||
#endif
|
||||
#if FF_FS_EXFAT
|
||||
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
|
||||
#endif
|
||||
#if !FF_FS_READONLY
|
||||
DWORD last_clst; /* Last allocated cluster */
|
||||
DWORD free_clst; /* Number of free clusters */
|
||||
#endif
|
||||
#if FF_FS_RPATH
|
||||
DWORD cdir; /* Current directory start cluster (0:root) */
|
||||
#if FF_FS_EXFAT
|
||||
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
|
||||
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
|
||||
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
|
||||
#endif
|
||||
#endif
|
||||
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
|
||||
DWORD fsize; /* Number of sectors per FAT */
|
||||
LBA_t volbase; /* Volume base sector */
|
||||
LBA_t fatbase; /* FAT base sector */
|
||||
LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
|
||||
LBA_t database; /* Data base sector */
|
||||
#if FF_FS_EXFAT
|
||||
LBA_t bitbase; /* Allocation bitmap base sector */
|
||||
#endif
|
||||
LBA_t winsect; /* Current sector appearing in the win[] */
|
||||
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
|
||||
} FATFS;
|
||||
|
||||
|
||||
|
||||
/* Object ID and allocation information (FFOBJID) */
|
||||
|
||||
typedef struct {
|
||||
FATFS* fs; /* Pointer to the hosting volume of this object */
|
||||
WORD id; /* Hosting volume's mount ID */
|
||||
BYTE attr; /* Object attribute */
|
||||
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
|
||||
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
|
||||
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
|
||||
#if FF_FS_EXFAT
|
||||
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
|
||||
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
|
||||
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
|
||||
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
|
||||
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
|
||||
#endif
|
||||
#if FF_FS_LOCK
|
||||
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
|
||||
#endif
|
||||
} FFOBJID;
|
||||
|
||||
|
||||
|
||||
/* File object structure (FIL) */
|
||||
|
||||
typedef struct {
|
||||
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
|
||||
BYTE flag; /* File status flags */
|
||||
BYTE err; /* Abort flag (error code) */
|
||||
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
|
||||
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
|
||||
LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */
|
||||
#if !FF_FS_READONLY
|
||||
LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
|
||||
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
|
||||
#endif
|
||||
#if FF_USE_FASTSEEK
|
||||
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
|
||||
#endif
|
||||
#if !FF_FS_TINY
|
||||
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
|
||||
#endif
|
||||
} FIL;
|
||||
|
||||
|
||||
|
||||
/* Directory object structure (FF_DIR) */
|
||||
|
||||
typedef struct {
|
||||
FFOBJID obj; /* Object identifier */
|
||||
DWORD dptr; /* Current read/write offset */
|
||||
DWORD clust; /* Current cluster */
|
||||
LBA_t sect; /* Current sector (0:Read operation has terminated) */
|
||||
BYTE* dir; /* Pointer to the directory item in the win[] */
|
||||
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
|
||||
#if FF_USE_LFN
|
||||
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
|
||||
#endif
|
||||
#if FF_USE_FIND
|
||||
const TCHAR* pat; /* Pointer to the name matching pattern */
|
||||
#endif
|
||||
} FF_DIR;
|
||||
|
||||
|
||||
|
||||
/* File information structure (FILINFO) */
|
||||
|
||||
typedef struct {
|
||||
FSIZE_t fsize; /* File size */
|
||||
WORD fdate; /* Modified date */
|
||||
WORD ftime; /* Modified time */
|
||||
BYTE fattrib; /* File attribute */
|
||||
#if FF_USE_LFN
|
||||
TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */
|
||||
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
|
||||
#else
|
||||
TCHAR fname[12 + 1]; /* File name */
|
||||
#endif
|
||||
} FILINFO;
|
||||
|
||||
|
||||
|
||||
/* Format parameter structure (MKFS_PARM) */
|
||||
|
||||
typedef struct {
|
||||
BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
|
||||
BYTE n_fat; /* Number of FATs */
|
||||
UINT align; /* Data area alignment (sector) */
|
||||
UINT n_root; /* Number of root directory entries */
|
||||
DWORD au_size; /* Cluster size (byte) */
|
||||
} MKFS_PARM;
|
||||
|
||||
|
||||
|
||||
/* File function return code (FRESULT) */
|
||||
|
||||
typedef enum {
|
||||
FR_OK = 0, /* (0) Succeeded */
|
||||
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
|
||||
FR_INT_ERR, /* (2) Assertion failed */
|
||||
FR_NOT_READY, /* (3) The physical drive cannot work */
|
||||
FR_NO_FILE, /* (4) Could not find the file */
|
||||
FR_NO_PATH, /* (5) Could not find the path */
|
||||
FR_INVALID_NAME, /* (6) The path name format is invalid */
|
||||
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
|
||||
FR_EXIST, /* (8) Access denied due to prohibited access */
|
||||
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
|
||||
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
|
||||
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
|
||||
FR_NOT_ENABLED, /* (12) The volume has no work area */
|
||||
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
|
||||
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
|
||||
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
|
||||
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
|
||||
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
|
||||
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
|
||||
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
|
||||
} FRESULT;
|
||||
|
||||
|
||||
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
/* FatFs Module Application Interface */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
|
||||
FRESULT f_close (FIL* fp); /* Close an open file object */
|
||||
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
|
||||
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
|
||||
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
|
||||
FRESULT f_truncate (FIL* fp); /* Truncate the file */
|
||||
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
|
||||
FRESULT f_opendir (FF_DIR* dp, const TCHAR* path); /* Open a directory */
|
||||
FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */
|
||||
FRESULT f_readdir (FF_DIR* dp, FILINFO* fno); /* Read a directory item */
|
||||
FRESULT f_findfirst (FF_DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
|
||||
FRESULT f_findnext (FF_DIR* dp, FILINFO* fno); /* Find next file */
|
||||
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
|
||||
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
|
||||
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
|
||||
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
|
||||
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
|
||||
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
|
||||
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
|
||||
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
|
||||
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
|
||||
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
|
||||
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
|
||||
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
|
||||
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
|
||||
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
|
||||
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
|
||||
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */
|
||||
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */
|
||||
FRESULT f_setcp (WORD cp); /* Set current code page */
|
||||
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
|
||||
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
|
||||
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
|
||||
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
|
||||
|
||||
/* Some API fucntions are implemented as macro */
|
||||
|
||||
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
|
||||
#define f_error(fp) ((fp)->err)
|
||||
#define f_tell(fp) ((fp)->fptr)
|
||||
#define f_size(fp) ((fp)->obj.objsize)
|
||||
#define f_rewind(fp) f_lseek((fp), 0)
|
||||
#define f_rewinddir(dp) f_readdir((dp), 0)
|
||||
#define f_rmdir(path) f_unlink(path)
|
||||
#define f_unmount(path) f_mount(0, path, 0)
|
||||
|
||||
|
||||
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
/* Additional Functions */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
/* RTC function (provided by user) */
|
||||
#if !FF_FS_READONLY && !FF_FS_NORTC
|
||||
DWORD get_fattime (void); /* Get current time */
|
||||
#endif
|
||||
|
||||
|
||||
/* LFN support functions (defined in ffunicode.c) */
|
||||
|
||||
#if FF_USE_LFN >= 1
|
||||
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
|
||||
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
|
||||
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
|
||||
#endif
|
||||
|
||||
|
||||
/* O/S dependent functions (samples available in ffsystem.c) */
|
||||
|
||||
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
|
||||
void* ff_memalloc (UINT msize); /* Allocate memory block */
|
||||
void ff_memfree (void* mblock); /* Free memory block */
|
||||
#endif
|
||||
#if FF_FS_REENTRANT /* Sync functions */
|
||||
int ff_mutex_create (int vol); /* Create a sync object */
|
||||
void ff_mutex_delete (int vol); /* Delete a sync object */
|
||||
int ff_mutex_take (int vol); /* Lock sync object */
|
||||
void ff_mutex_give (int vol); /* Unlock sync object */
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
/* Flags and Offset Address */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
/* File access mode and open method flags (3rd argument of f_open) */
|
||||
#define FA_READ 0x01
|
||||
#define FA_WRITE 0x02
|
||||
#define FA_OPEN_EXISTING 0x00
|
||||
#define FA_CREATE_NEW 0x04
|
||||
#define FA_CREATE_ALWAYS 0x08
|
||||
#define FA_OPEN_ALWAYS 0x10
|
||||
#define FA_OPEN_APPEND 0x30
|
||||
|
||||
/* Fast seek controls (2nd argument of f_lseek) */
|
||||
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
|
||||
|
||||
/* Format options (2nd argument of f_mkfs) */
|
||||
#define FM_FAT 0x01
|
||||
#define FM_FAT32 0x02
|
||||
#define FM_EXFAT 0x04
|
||||
#define FM_ANY 0x07
|
||||
#define FM_SFD 0x08
|
||||
|
||||
/* Filesystem type (FATFS.fs_type) */
|
||||
#define FS_FAT12 1
|
||||
#define FS_FAT16 2
|
||||
#define FS_FAT32 3
|
||||
#define FS_EXFAT 4
|
||||
|
||||
/* File attribute bits for directory entry (FILINFO.fattrib) */
|
||||
#define AM_RDO 0x01 /* Read only */
|
||||
#define AM_HID 0x02 /* Hidden */
|
||||
#define AM_SYS 0x04 /* System */
|
||||
#define AM_DIR 0x10 /* Directory */
|
||||
#define AM_ARC 0x20 /* Archive */
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FF_DEFINED */
|
||||
@@ -0,0 +1,332 @@
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Configurations of FatFs Module
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 80286 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_READONLY 0
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 0
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 1
|
||||
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FASTSEEK 1
|
||||
/* This option switches fast seek function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 1
|
||||
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 0
|
||||
/* This option switches volume label functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 1
|
||||
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 0
|
||||
#define FF_PRINT_LLI 0
|
||||
#define FF_PRINT_FLOAT 0
|
||||
#define FF_STRF_ENCODE 3
|
||||
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
|
||||
/ f_printf().
|
||||
/
|
||||
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion.
|
||||
/
|
||||
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
|
||||
/ makes f_printf() support floating point argument. These features want C99 or later.
|
||||
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
|
||||
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||
/ to be read/written via those functions.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP
|
||||
/ 1: Unicode in UTF-16LE
|
||||
/ 2: Unicode in UTF-16BE
|
||||
/ 3: Unicode in UTF-8
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_CODE_PAGE CONFIG_FATFS_CODEPAGE
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect code page setting can cause a file open failure.
|
||||
/
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
/ 0 - Include all code pages above and configured by f_setcp()
|
||||
*/
|
||||
|
||||
|
||||
#if defined(CONFIG_FATFS_LFN_STACK)
|
||||
#define FF_USE_LFN 2
|
||||
#elif defined(CONFIG_FATFS_LFN_HEAP)
|
||||
#define FF_USE_LFN 3
|
||||
#else /* CONFIG_FATFS_LFN_NONE */
|
||||
#define FF_USE_LFN 0
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FATFS_MAX_LFN
|
||||
#define FF_MAX_LFN CONFIG_FATFS_MAX_LFN
|
||||
#endif
|
||||
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#ifdef CONFIG_FATFS_API_ENCODING_UTF_8
|
||||
#define FF_LFN_UNICODE 2
|
||||
#else /* CONFIG_FATFS_API_ENCODING_ANSI_OEM */
|
||||
#define FF_LFN_UNICODE 0
|
||||
#endif
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||
/
|
||||
/ Also behavior of string I/O functions will be affected by this option.
|
||||
/ When LFN is not enabled, this option has no effect. */
|
||||
|
||||
|
||||
#define FF_LFN_BUF 255
|
||||
#define FF_SFN_BUF 12
|
||||
/* This set of options defines size of file name members in the FILINFO structure
|
||||
/ which is used to read out directory items. These values should be suffcient for
|
||||
/ the file names to read. The maximum possible length of the read file name depends
|
||||
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||
|
||||
|
||||
#define FF_FS_RPATH 0
|
||||
/* This option configures support for relative path.
|
||||
/
|
||||
/ 0: Disable relative path and remove related functions.
|
||||
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() function is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_VOLUMES CONFIG_FATFS_VOLUME_COUNT
|
||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 0
|
||||
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table is needed as:
|
||||
/
|
||||
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
|
||||
*/
|
||||
|
||||
|
||||
#define FF_MULTI_PARTITION 1
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this function is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ function will be available. */
|
||||
|
||||
/* SD card sector size */
|
||||
#define FF_SS_SDCARD 512
|
||||
/* wear_levelling library sector size */
|
||||
#define FF_SS_WL CONFIG_WL_SECTOR_SIZE
|
||||
|
||||
#define FF_MIN_SS MIN(FF_SS_SDCARD, FF_SS_WL)
|
||||
#define FF_MAX_SS MAX(FF_SS_SDCARD, FF_SS_WL)
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk, but a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
|
||||
/ for variable sector size mode and disk_ioctl() function needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_LBA64 1
|
||||
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
|
||||
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||
|
||||
|
||||
#define FF_MIN_GPT 0x10000000
|
||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
|
||||
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 1
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
|
||||
/ disk_ioctl() function. */
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_TINY (!CONFIG_FATFS_PER_FILE_CACHE)
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||
|
||||
|
||||
#define FF_FS_EXFAT 1
|
||||
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
|
||||
#define FF_FS_NORTC 0
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2022
|
||||
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
|
||||
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
|
||||
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
|
||||
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 0
|
||||
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
||||
/ option, and f_getfree() function at the first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_FS_LOCK CONFIG_FATFS_FS_LOCK
|
||||
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
|
||||
#define FF_FS_REENTRANT 1
|
||||
#define FF_FS_TIMEOUT (portMAX_DELAY)
|
||||
/* The option FF_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()
|
||||
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
|
||||
/ to the same volume is under control of this featuer.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
|
||||
/ function, must be added to the project. Samples are available in ffsystem.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
/* Some memory allocation functions are declared here in addition to ff.h, so that
|
||||
they can be used also by external code when LFN feature is disabled.
|
||||
*/
|
||||
void* ff_memalloc (unsigned msize);
|
||||
void ff_memfree(void*);
|
||||
|
||||
|
||||
/*--- End of configuration options ---*/
|
||||
|
||||
/* Redefine names of disk IO functions to prevent name collisions */
|
||||
#define disk_initialize ff_disk_initialize
|
||||
#define disk_status ff_disk_status
|
||||
#define disk_read ff_disk_read
|
||||
#define disk_write ff_disk_write
|
||||
#define disk_ioctl ff_disk_ioctl
|
||||
@@ -0,0 +1,208 @@
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* A Sample Code of User Provided OS Dependent Functions for FatFs */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
|
||||
#if FF_USE_LFN == 3 /* Use dynamic memory allocation */
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Allocate/Free a Memory Block */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
#include <stdlib.h> /* with POSIX API */
|
||||
|
||||
|
||||
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
|
||||
UINT msize /* Number of bytes to allocate */
|
||||
)
|
||||
{
|
||||
return malloc((size_t)msize); /* Allocate a new memory block */
|
||||
}
|
||||
|
||||
|
||||
void ff_memfree (
|
||||
void* mblock /* Pointer to the memory block to free (no effect if null) */
|
||||
)
|
||||
{
|
||||
free(mblock); /* Free the memory block */
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#if FF_FS_REENTRANT /* Mutal exclusion */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Definitions of Mutex */
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
#define OS_TYPE 0 /* 0:Win32, 1:uITRON4.0, 2:uC/OS-II, 3:FreeRTOS, 4:CMSIS-RTOS */
|
||||
|
||||
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
#include <windows.h>
|
||||
static HANDLE Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
#include "itron.h"
|
||||
#include "kernel.h"
|
||||
static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
|
||||
|
||||
#elif OS_TYPE == 2 /* uc/OS-II */
|
||||
#include "includes.h"
|
||||
static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
static SemaphoreHandle_t Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
#include "cmsis_os.h"
|
||||
static osMutexId Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Create a Mutex */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called in f_mount function to create a new mutex
|
||||
/ or semaphore for the volume. When a 0 is returned, the f_mount function
|
||||
/ fails with FR_INT_ERR.
|
||||
*/
|
||||
|
||||
int ff_mutex_create ( /* Returns 1:Function succeeded or 0:Could not create the mutex */
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
Mutex[vol] = CreateMutex(NULL, FALSE, NULL);
|
||||
return (int)(Mutex[vol] != INVALID_HANDLE_VALUE);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
T_CMTX cmtx = {TA_TPRI,1};
|
||||
|
||||
Mutex[vol] = acre_mtx(&cmtx);
|
||||
return (int)(Mutex[vol] > 0);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OS_ERR err;
|
||||
|
||||
Mutex[vol] = OSMutexCreate(0, &err);
|
||||
return (int)(err == OS_NO_ERR);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
Mutex[vol] = xSemaphoreCreateMutex();
|
||||
return (int)(Mutex[vol] != NULL);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
osMutexDef(cmsis_os_mutex);
|
||||
|
||||
Mutex[vol] = osMutexCreate(osMutex(cmsis_os_mutex));
|
||||
return (int)(Mutex[vol] != NULL);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Delete a Mutex */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called in f_mount function to delete a mutex or
|
||||
/ semaphore of the volume created with ff_mutex_create function.
|
||||
*/
|
||||
|
||||
void ff_mutex_delete ( /* Returns 1:Function succeeded or 0:Could not delete due to an error */
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
CloseHandle(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
del_mtx(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OS_ERR err;
|
||||
|
||||
OSMutexDel(Mutex[vol], OS_DEL_ALWAYS, &err);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
vSemaphoreDelete(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
osMutexDelete(Mutex[vol]);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Request a Grant to Access the Volume */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called on enter file functions to lock the volume.
|
||||
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
|
||||
*/
|
||||
|
||||
int ff_mutex_take ( /* Returns 1:Succeeded or 0:Timeout */
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
return (int)(WaitForSingleObject(Mutex[vol], FF_FS_TIMEOUT) == WAIT_OBJECT_0);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
return (int)(tloc_mtx(Mutex[vol], FF_FS_TIMEOUT) == E_OK);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OS_ERR err;
|
||||
|
||||
OSMutexPend(Mutex[vol], FF_FS_TIMEOUT, &err));
|
||||
return (int)(err == OS_NO_ERR);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
return (int)(xSemaphoreTake(Mutex[vol], FF_FS_TIMEOUT) == pdTRUE);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
return (int)(osMutexWait(Mutex[vol], FF_FS_TIMEOUT) == osOK);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* Release a Grant to Access the Volume */
|
||||
/*------------------------------------------------------------------------*/
|
||||
/* This function is called on leave file functions to unlock the volume.
|
||||
*/
|
||||
|
||||
void ff_mutex_give (
|
||||
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||
)
|
||||
{
|
||||
#if OS_TYPE == 0 /* Win32 */
|
||||
ReleaseMutex(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 1 /* uITRON */
|
||||
unl_mtx(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||
OSMutexPost(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||
xSemaphoreGive(Mutex[vol]);
|
||||
|
||||
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||
osMutexRelease(Mutex[vol]);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* FF_FS_REENTRANT */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
# fatfs component target tests
|
||||
|
||||
This directory contains tests for `fatfs` component which are run on chip targets.
|
||||
|
||||
See also [test_fatfs_host](../test_fatfs_host) directory for the tests which run on a Linux host.
|
||||
|
||||
Fatfs tests can be executed with different `diskio` backends: `diskio_sdmmc` (SD cards over SD or SPI interface), `diskio_spiflash` (wear levelling in internal flash) and `diskio_rawflash` (read-only, no wear levelling, internal flash). There is one test app here for each of these backends:
|
||||
|
||||
- [sdcard](sdcard/) — runs fatfs tests with an SD card over SDMMC or SDSPI interface
|
||||
- [flash_wl](flash_wl/) - runs fatfs test in a wear_levelling partition in SPI flash
|
||||
- [flash_ro](flash_ro/) - runs fatfs test in a read-only (no wear levelling) partition in SPI flash
|
||||
|
||||
These test apps define:
|
||||
- test functions
|
||||
- setup/teardown routines
|
||||
- build/test configurations
|
||||
- pytest test runners
|
||||
|
||||
The actual test cases (many of which are common between the test apps) are defined in the [test_fatfs_common component](test_fatfs_common/).
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(COMPONENTS main)
|
||||
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../test_fatfs_common")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(test_fatfs_flash_ro)
|
||||
@@ -0,0 +1,14 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
This test app runs a few FATFS test cases in a read-only FAT partition.
|
||||
|
||||
These tests should be possible to run on any ESP development board, not extra hardware is necessary.
|
||||
|
||||
The initial FAT image is generated during the build process in [main/CMakeLists.txt](main/CMakeLists.txt):
|
||||
- `create_test_files` function creates a set of files expected by the test cases
|
||||
- `fatfs_create_rawflash_image` generates a FAT image from the set of files (via `fatfsgen.py`)
|
||||
|
||||
The generated FAT image is flashed into `storage` partition when running `idf.py flash`.
|
||||
|
||||
See [../README.md](../README.md) for more information about FATFS test apps.
|
||||
@@ -0,0 +1,41 @@
|
||||
idf_component_register(SRCS "test_fatfs_flash_ro.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity spi_flash fatfs vfs test_fatfs_common
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
|
||||
set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/fatfs_image")
|
||||
|
||||
# This helper function creates a set of files expected by the test case.
|
||||
# The files are then added into the FAT image by 'fatfs_create_rawflash_image' below.
|
||||
function(create_test_files)
|
||||
message(STATUS "Generating source files for test_fatfs_flash_ro in ${out_dir}...")
|
||||
|
||||
# used in "(raw) can read file"
|
||||
file(WRITE "${out_dir}/hello.txt" "Hello, World!\n")
|
||||
|
||||
# used in "(raw) can open maximum number of files"
|
||||
foreach(i RANGE 1 32)
|
||||
file(WRITE "${out_dir}/f/${i}.txt")
|
||||
endforeach()
|
||||
|
||||
# used in "(raw) opendir, readdir, rewinddir, seekdir work as expected"
|
||||
file(WRITE "${out_dir}/dir/1.txt")
|
||||
file(WRITE "${out_dir}/dir/2.txt")
|
||||
file(WRITE "${out_dir}/dir/boo.bin")
|
||||
file(WRITE "${out_dir}/dir/inner/3.txt")
|
||||
|
||||
# used in "(raw) multiple tasks can use same volume"
|
||||
foreach(i RANGE 1 4)
|
||||
string(REPEAT "${i}" 32000 file_content)
|
||||
file(WRITE "${out_dir}/ccrnt/${i}.txt" ${file_content})
|
||||
endforeach()
|
||||
|
||||
# used in "(raw) read speed test"
|
||||
string(REPEAT "a" 262144 file_content)
|
||||
file(WRITE "${out_dir}/256k.bin" ${file_content})
|
||||
endfunction()
|
||||
|
||||
create_test_files()
|
||||
|
||||
fatfs_create_rawflash_image(storage ${out_dir} FLASH_IN_PROJECT PRESERVE_TIME)
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include "unity.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "test_fatfs_common.h"
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
|
||||
static void test_setup(size_t max_files)
|
||||
{
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = false,
|
||||
.max_files = max_files
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount_ro("/spiflash", "storage", &mount_config));
|
||||
}
|
||||
|
||||
static void test_teardown(void)
|
||||
{
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_ro("/spiflash", "storage"));
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) can read file", "[fatfs]")
|
||||
{
|
||||
test_setup(5);
|
||||
FILE* f = fopen("/spiflash/hello.txt", "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
char buf[32] = { 0 };
|
||||
int cb = fread(buf, 1, sizeof(buf), f);
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), cb);
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) can open maximum number of files", "[fatfs]")
|
||||
{
|
||||
size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */
|
||||
test_setup(max_files);
|
||||
|
||||
FILE** files = calloc(max_files, sizeof(FILE*));
|
||||
for (size_t i = 0; i < max_files; ++i) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "/spiflash/f/%d.txt", i + 1);
|
||||
files[i] = fopen(name, "r");
|
||||
TEST_ASSERT_NOT_NULL(files[i]);
|
||||
}
|
||||
/* close everything and clean up */
|
||||
for (size_t i = 0; i < max_files; ++i) {
|
||||
fclose(files[i]);
|
||||
}
|
||||
free(files);
|
||||
test_teardown();
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) can lseek", "[fatfs]")
|
||||
{
|
||||
test_setup(5);
|
||||
FILE* f = fopen("/spiflash/hello.txt", "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, 2, SEEK_CUR));
|
||||
TEST_ASSERT_EQUAL('l', fgetc(f));
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, 4, SEEK_SET));
|
||||
TEST_ASSERT_EQUAL('o', fgetc(f));
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, -5, SEEK_END));
|
||||
TEST_ASSERT_EQUAL('r', fgetc(f));
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END));
|
||||
TEST_ASSERT_EQUAL(17, ftell(f));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
|
||||
TEST_ASSERT_EQUAL(14, ftell(f));
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET));
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) stat returns correct values", "[fatfs]")
|
||||
{
|
||||
test_setup(5);
|
||||
struct tm tm;
|
||||
tm.tm_year = 2018 - 1900;
|
||||
tm.tm_mon = 5; // Note: month can be 0-11 & not 1-12
|
||||
tm.tm_mday = 13;
|
||||
tm.tm_hour = 11;
|
||||
tm.tm_min = 2;
|
||||
tm.tm_sec = 10;
|
||||
time_t t = mktime(&tm);
|
||||
printf("Reference time: %s", asctime(&tm));
|
||||
|
||||
struct stat st;
|
||||
TEST_ASSERT_EQUAL(0, stat("/spiflash/hello.txt", &st));
|
||||
|
||||
time_t mtime = st.st_mtime;
|
||||
struct tm mtm;
|
||||
localtime_r(&mtime, &mtm);
|
||||
printf("File time: %s", asctime(&mtm));
|
||||
TEST_ASSERT(mtime > t); // Modification time should be in future wrt ref time
|
||||
|
||||
TEST_ASSERT(st.st_mode & S_IFREG);
|
||||
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
TEST_ASSERT_EQUAL(0, stat("/spiflash", &st));
|
||||
TEST_ASSERT(st.st_mode & S_IFDIR);
|
||||
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
||||
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) can opendir root directory of FS", "[fatfs]")
|
||||
{
|
||||
test_setup(5);
|
||||
DIR* dir = opendir("/spiflash");
|
||||
TEST_ASSERT_NOT_NULL(dir);
|
||||
bool found = false;
|
||||
while (true) {
|
||||
struct dirent* de = readdir(dir);
|
||||
if (!de) {
|
||||
break;
|
||||
}
|
||||
if (strcasecmp(de->d_name, "hello.txt") == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_TRUE(found);
|
||||
TEST_ASSERT_EQUAL(0, closedir(dir));
|
||||
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs]")
|
||||
{
|
||||
test_setup(5);
|
||||
|
||||
DIR* dir = opendir("/spiflash/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_teardown();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char* filename;
|
||||
size_t word_count;
|
||||
unsigned val;
|
||||
SemaphoreHandle_t done;
|
||||
esp_err_t result;
|
||||
} read_test_arg_t;
|
||||
|
||||
#define READ_TEST_ARG_INIT(name, val_) \
|
||||
{ \
|
||||
.filename = name, \
|
||||
.word_count = 8000, \
|
||||
.val = val_, \
|
||||
.done = xSemaphoreCreateBinary() \
|
||||
}
|
||||
|
||||
static void read_task(void* param)
|
||||
{
|
||||
read_test_arg_t* args = (read_test_arg_t*) param;
|
||||
FILE* f = fopen(args->filename, "rb");
|
||||
if (f == NULL) {
|
||||
args->result = ESP_ERR_NOT_FOUND;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < args->word_count; ++i) {
|
||||
unsigned rval;
|
||||
int cnt = fread(&rval, sizeof(rval), 1, f);
|
||||
if (cnt != 1 || rval != args->val) {
|
||||
printf("E(r): i=%d, cnt=%d rval=0x08%x val=0x%08x\n", i, cnt, rval, args->val);
|
||||
args->result = ESP_FAIL;
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
args->result = ESP_OK;
|
||||
|
||||
close:
|
||||
fclose(f);
|
||||
|
||||
done:
|
||||
xSemaphoreGive(args->done);
|
||||
vTaskDelay(1);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) multiple tasks can use same volume", "[fatfs]")
|
||||
{
|
||||
test_setup(5);
|
||||
char names[4][64];
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
snprintf(names[i], sizeof(names[i]), "/spiflash/ccrnt/%d.txt", i + 1);
|
||||
}
|
||||
|
||||
read_test_arg_t args1 = READ_TEST_ARG_INIT(names[0], 0x31313131);
|
||||
read_test_arg_t args2 = READ_TEST_ARG_INIT(names[1], 0x32323232);
|
||||
read_test_arg_t args3 = READ_TEST_ARG_INIT(names[2], 0x33333333);
|
||||
read_test_arg_t args4 = READ_TEST_ARG_INIT(names[3], 0x34343434);
|
||||
|
||||
const int cpuid_0 = 0;
|
||||
const int cpuid_1 = portNUM_PROCESSORS - 1;
|
||||
const int stack_size = 4096;
|
||||
|
||||
printf("reading files 1.txt 2.txt 3.txt 4.txt \n");
|
||||
|
||||
xTaskCreatePinnedToCore(&read_task, "r1", stack_size, &args1, 3, NULL, cpuid_1);
|
||||
xTaskCreatePinnedToCore(&read_task, "r2", stack_size, &args2, 3, NULL, cpuid_0);
|
||||
xTaskCreatePinnedToCore(&read_task, "r3", stack_size, &args3, 3, NULL, cpuid_0);
|
||||
xTaskCreatePinnedToCore(&read_task, "r4", stack_size, &args4, 3, NULL, cpuid_1);
|
||||
|
||||
xSemaphoreTake(args1.done, portMAX_DELAY);
|
||||
printf("1.txt done\n");
|
||||
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
|
||||
xSemaphoreTake(args2.done, portMAX_DELAY);
|
||||
printf("2.txt done\n");
|
||||
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
|
||||
xSemaphoreTake(args3.done, portMAX_DELAY);
|
||||
printf("3.txt done\n");
|
||||
TEST_ASSERT_EQUAL(ESP_OK, args3.result);
|
||||
xSemaphoreTake(args4.done, portMAX_DELAY);
|
||||
printf("4.txt done\n");
|
||||
TEST_ASSERT_EQUAL(ESP_OK, args4.result);
|
||||
|
||||
vSemaphoreDelete(args1.done);
|
||||
vSemaphoreDelete(args2.done);
|
||||
vSemaphoreDelete(args3.done);
|
||||
vSemaphoreDelete(args4.done);
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(raw) read speed test", "[fatfs][timeout=60]")
|
||||
{
|
||||
test_setup(5);
|
||||
|
||||
const size_t buf_size = 16 * 1024;
|
||||
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
|
||||
const size_t file_size = 256 * 1024;
|
||||
const char* file = "/spiflash/256k.bin";
|
||||
|
||||
test_fatfs_rw_speed(file, buf, 4 * 1024, file_size, false);
|
||||
test_fatfs_rw_speed(file, buf, 8 * 1024, file_size, false);
|
||||
test_fatfs_rw_speed(file, buf, 16 * 1024, file_size, false);
|
||||
|
||||
free(buf);
|
||||
test_teardown();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 528k,
|
||||
|
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.supported_targets
|
||||
@pytest.mark.generic
|
||||
def test_fatfs_flash_ro(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output()
|
||||
@@ -0,0 +1,14 @@
|
||||
# General options for additional checks
|
||||
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
|
||||
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
|
||||
CONFIG_COMPILER_STACK_CHECK=y
|
||||
|
||||
# disable task watchdog since this app uses an interactive menu
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
# use custom partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(COMPONENTS main)
|
||||
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../test_fatfs_common")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(test_fatfs_flash_wl)
|
||||
@@ -0,0 +1,8 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
This test app runs a few FATFS test cases in a wear levelling FAT partition.
|
||||
|
||||
These tests should be possible to run on any ESP development board, not extra hardware is necessary.
|
||||
|
||||
See [../README.md](../README.md) for more information about FATFS test apps.
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "test_fatfs_flash_wl.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity spi_flash fatfs vfs test_fatfs_common
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include "unity.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "test_fatfs_common.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_memory_utils.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
|
||||
static wl_handle_t s_test_wl_handle;
|
||||
static void test_setup(void)
|
||||
{
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &s_test_wl_handle));
|
||||
}
|
||||
|
||||
static void test_teardown(void)
|
||||
{
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", s_test_wl_handle));
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can format partition", "[fatfs][wear_levelling]")
|
||||
{
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL));
|
||||
test_setup();
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL));
|
||||
test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str);
|
||||
test_fatfs_pread_file("/spiflash/hello.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can create and write file", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str);
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can create and open file with O_CREAT flag", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_create_file_with_o_creat_flag("/spiflash/hello.txt");
|
||||
test_fatfs_open_file_with_o_creat_flag("/spiflash/hello.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can read file", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str);
|
||||
test_fatfs_read_file("/spiflash/hello.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can read file with pread", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str);
|
||||
test_fatfs_pread_file("/spiflash/hello.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) pwrite() works well", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_pwrite_file("/spiflash/hello.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can open maximum number of files", "[fatfs][wear_levelling]")
|
||||
{
|
||||
size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = max_files
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &s_test_wl_handle));
|
||||
test_fatfs_open_max_files("/spiflash/f", max_files);
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", s_test_wl_handle));
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) overwrite and append file", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_overwrite_append("/spiflash/hello.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can lseek", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_lseek("/spiflash/seek.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can truncate", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_truncate_file("/spiflash/truncate.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_stat("/spiflash/stat.txt", "/spiflash");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) stat returns correct mtime if DST is enabled", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_mtime_dst("/spiflash/statdst.txt", "/spiflash");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) utime sets modification time", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_utime("/spiflash/utime.txt", "/spiflash");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) unlink removes a file", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_unlink("/spiflash/unlink.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) link copies a file, rename moves a file", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_link_rename("/spiflash/link");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can create and remove directories", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_mkdir_rmdir("/spiflash/dir");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can opendir root directory of FS", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_can_opendir("/spiflash");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_opendir_readdir_rewinddir("/spiflash/dir");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) multiple tasks can use same volume", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_concurrent("/spiflash/f");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) fatfs does not ignore leading spaces", "[fatfs][wear_levelling]")
|
||||
{
|
||||
// the functionality of ignoring leading and trailing whitespaces is not implemented yet
|
||||
// when the feature is implemented, this test will fail
|
||||
// please, remove the test and implement the functionality in fatfsgen.py to preserve the consistency
|
||||
test_setup();
|
||||
test_leading_spaces();
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("(WL) write/read speed test", "[fatfs][wear_levelling][timeout=60]")
|
||||
{
|
||||
/* Erase partition before running the test to get consistent results */
|
||||
const esp_partition_t* part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
|
||||
ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
|
||||
esp_partition_erase_range(part, 0, part->size);
|
||||
|
||||
test_setup();
|
||||
|
||||
const size_t buf_size = 16 * 1024;
|
||||
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
|
||||
esp_fill_random(buf, buf_size);
|
||||
const size_t file_size = 256 * 1024;
|
||||
const char* file = "/spiflash/256k.bin";
|
||||
|
||||
test_fatfs_rw_speed(file, buf, 4 * 1024, file_size, true);
|
||||
test_fatfs_rw_speed(file, buf, 8 * 1024, file_size, true);
|
||||
test_fatfs_rw_speed(file, buf, 16 * 1024, file_size, true);
|
||||
|
||||
test_fatfs_rw_speed(file, buf, 4 * 1024, file_size, false);
|
||||
test_fatfs_rw_speed(file, buf, 8 * 1024, file_size, false);
|
||||
test_fatfs_rw_speed(file, buf, 16 * 1024, file_size, false);
|
||||
|
||||
unlink(file);
|
||||
|
||||
free(buf);
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) can get partition info", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_info("/spiflash", "/spiflash/test.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
/*
|
||||
* In FatFs menuconfig, set CONFIG_FATFS_API_ENCODING to UTF-8 and set the
|
||||
* Codepage to CP936 (Simplified Chinese) in order to run the following tests.
|
||||
* Ensure that the text editor is UTF-8 compatible when compiling these tests.
|
||||
*/
|
||||
#if defined(CONFIG_FATFS_API_ENCODING_UTF_8) && (CONFIG_FATFS_CODEPAGE == 936)
|
||||
TEST_CASE("(WL) can read file with UTF-8 encoded strings", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_create_file_with_text("/spiflash/测试文件.txt", fatfs_test_hello_str_utf);
|
||||
test_fatfs_read_file_utf_8("/spiflash/测试文件.txt");
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
TEST_CASE("(WL) opendir, readdir, rewinddir, seekdir work as expected using UTF-8 encoded strings", "[fatfs][wear_levelling]")
|
||||
{
|
||||
test_setup();
|
||||
test_fatfs_opendir_readdir_rewinddir_utf_8("/spiflash/目录");
|
||||
test_teardown();
|
||||
}
|
||||
#endif //defined(CONFIG_FATFS_API_ENCODING_UTF_8) && (CONFIG_FATFS_CODEPAGE == 936)
|
||||
|
||||
#ifdef CONFIG_SPIRAM
|
||||
TEST_CASE("FATFS prefers SPI RAM for allocations", "[fatfs]")
|
||||
{
|
||||
test_setup();
|
||||
DIR* dir = opendir("/spiflash");
|
||||
TEST_ASSERT_NOT_NULL(dir);
|
||||
TEST_ASSERT(esp_ptr_external_ram(dir));
|
||||
closedir(dir);
|
||||
test_teardown();
|
||||
}
|
||||
#endif // CONFIG_SPIRAM
|
||||
@@ -0,0 +1,3 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 528k,
|
||||
|
@@ -0,0 +1,39 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.supported_targets
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'default',
|
||||
'release',
|
||||
'fastseek',
|
||||
]
|
||||
)
|
||||
def test_fatfs_flash_wl_generic(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.supported_targets
|
||||
@pytest.mark.psram
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'psram',
|
||||
]
|
||||
)
|
||||
def test_fatfs_flash_wl_psram(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_FATFS_USE_FASTSEEK=y
|
||||
CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE=64
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
||||
@@ -0,0 +1,18 @@
|
||||
# General options for additional checks
|
||||
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
|
||||
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
|
||||
CONFIG_COMPILER_STACK_CHECK=y
|
||||
|
||||
# disable task watchdog since this app uses an interactive menu
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
# use custom partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
|
||||
# some tests verify file name encoding
|
||||
CONFIG_FATFS_API_ENCODING_UTF_8=y
|
||||
CONFIG_FATFS_CODEPAGE_936=y
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(COMPONENTS main)
|
||||
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../test_fatfs_common")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(test_fatfs_sdcard)
|
||||
@@ -0,0 +1,14 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
This test app runs a few FATFS test cases in a FAT-formatted SD card.
|
||||
|
||||
These tests require a development board with an SD card slot:
|
||||
|
||||
* ESP32-WROVER-KIT
|
||||
* ESP32-S2 USB_OTG
|
||||
* ESP32-C3-DevKit-C with an SD card breakout board
|
||||
|
||||
The test cases are split between `[sdmmc]` and `[sdspi]`. Only a few tests are executed for sdspi, though. The app could be refactored to ensure that a similar set of tests runs for sdmmc and sdspi.
|
||||
|
||||
See [../README.md](../README.md) for more information about FATFS test apps.
|
||||
@@ -0,0 +1,8 @@
|
||||
idf_component_register(SRCS "test_fatfs_sdcard_main.c" "test_fatfs_sdspi.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity fatfs vfs sdmmc driver test_fatfs_common
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
if(CONFIG_SOC_SDMMC_HOST_SUPPORTED)
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "test_fatfs_sdmmc.c")
|
||||
endif()
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "unity.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include "unity.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "ff.h"
|
||||
#include "test_fatfs_common.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define SDSPI_MISO_PIN 2
|
||||
#define SDSPI_MOSI_PIN 15
|
||||
#define SDSPI_CLK_PIN 14
|
||||
#define SDSPI_CS_PIN 13
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
// Adapted for internal test board ESP-32-S3-USB-OTG-Ev-BOARD_V1.0 (with ESP32-S2-MINI-1 module)
|
||||
#define SDSPI_MISO_PIN 37
|
||||
#define SDSPI_MOSI_PIN 35
|
||||
#define SDSPI_CLK_PIN 36
|
||||
#define SDSPI_CS_PIN 34
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
#define SDSPI_MISO_PIN 6
|
||||
#define SDSPI_MOSI_PIN 4
|
||||
#define SDSPI_CLK_PIN 5
|
||||
#define SDSPI_CS_PIN 1
|
||||
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
|
||||
#endif //CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32C3
|
||||
|
||||
#ifndef SPI_DMA_CHAN
|
||||
#define SPI_DMA_CHAN 1
|
||||
#endif //SPI_DMA_CHAN
|
||||
#define SDSPI_HOST_ID SPI2_HOST
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3)
|
||||
// No runner
|
||||
#include "driver/sdmmc_host.h"
|
||||
|
||||
static void test_setup_sdmmc(sdmmc_card_t **out_card)
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
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,
|
||||
.allocation_unit_size = 16 * 1024
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card));
|
||||
*out_card = card;
|
||||
}
|
||||
|
||||
static void test_teardown_sdmmc(sdmmc_card_t *card)
|
||||
{
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount("/sdcard", card));
|
||||
}
|
||||
|
||||
static const char* test_filename = "/sdcard/hello.txt";
|
||||
|
||||
TEST_CASE("Mount fails cleanly without card inserted", "[fatfs][ignore]")
|
||||
{
|
||||
size_t heap_size;
|
||||
HEAP_SIZE_CAPTURE(heap_size);
|
||||
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
|
||||
};
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
printf("Initializing card, attempt %d\n", i);
|
||||
esp_err_t err = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL);
|
||||
printf("err=%d\n", err);
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, err);
|
||||
}
|
||||
HEAP_SIZE_CHECK(heap_size, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can format partition", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_format("/sdcard", card));
|
||||
test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str);
|
||||
test_fatfs_read_file(test_filename);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can create and write file", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can read file", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str);
|
||||
test_fatfs_read_file(test_filename);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can read file with pread()", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str);
|
||||
test_fatfs_pread_file(test_filename);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) pwrite() works well", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_pwrite_file(test_filename);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) overwrite and append file", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_overwrite_append(test_filename);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can lseek", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_lseek("/sdcard/seek.txt");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can truncate", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_truncate_file("/sdcard/truncate.txt");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can ftruncate", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_ftruncate_file("/sdcard/ftrunc.txt");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) stat returns correct values", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_stat("/sdcard/stat.txt", "/sdcard");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) utime sets modification time", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_utime("/sdcard/utime.txt", "/sdcard");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) unlink removes a file", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_unlink("/sdcard/unlink.txt");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) link copies a file, rename moves a file", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_link_rename("/sdcard/link");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can create and remove directories", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_mkdir_rmdir("/sdcard/dir");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) can opendir root directory of FS", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_can_opendir("/sdcard");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_opendir_readdir_rewinddir("/sdcard/dir");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) multiple tasks can use same volume", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_concurrent("/sdcard/f");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool write);
|
||||
|
||||
TEST_CASE("(SD) write/read speed test", "[fatfs][sdmmc]")
|
||||
{
|
||||
size_t heap_size;
|
||||
HEAP_SIZE_CAPTURE(heap_size);
|
||||
|
||||
const size_t buf_size = 16 * 1024;
|
||||
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
|
||||
esp_fill_random(buf, buf_size);
|
||||
const size_t file_size = 1 * 1024 * 1024;
|
||||
|
||||
sdmmc_speed_test(buf, 4 * 1024, file_size, true);
|
||||
sdmmc_speed_test(buf, 8 * 1024, file_size, true);
|
||||
sdmmc_speed_test(buf, 16 * 1024, file_size, true);
|
||||
|
||||
sdmmc_speed_test(buf, 4 * 1024, file_size, false);
|
||||
sdmmc_speed_test(buf, 8 * 1024, file_size, false);
|
||||
sdmmc_speed_test(buf, 16 * 1024, file_size, false);
|
||||
|
||||
free(buf);
|
||||
|
||||
HEAP_SIZE_CHECK(heap_size, 0);
|
||||
}
|
||||
|
||||
static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool write)
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
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,
|
||||
.allocation_unit_size = 64 * 1024
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card));
|
||||
|
||||
test_fatfs_rw_speed("/sdcard/4mb.bin", buf, buf_size, file_size, write);
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount("/sdcard", card));
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time", "[fatfs][sdmmc]")
|
||||
{
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5
|
||||
};
|
||||
|
||||
const char* filename_sd = "/sdcard/sd.txt";
|
||||
const char* filename_wl = "/spiflash/wl.txt";
|
||||
const char* str_sd = "this is sd\n";
|
||||
const char* str_wl = "this is spiflash\n";
|
||||
|
||||
/* Erase flash before the first use */
|
||||
const esp_partition_t *test_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
|
||||
TEST_ASSERT_NOT_NULL(test_partition);
|
||||
esp_partition_erase_range(test_partition, 0, test_partition->size);
|
||||
|
||||
/* Mount FATFS in SD can WL at the same time. Create a file on each FS */
|
||||
wl_handle_t wl_handle = WL_INVALID_HANDLE;
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &wl_handle));
|
||||
unlink(filename_sd);
|
||||
unlink(filename_wl);
|
||||
test_fatfs_create_file_with_text(filename_sd, str_sd);
|
||||
test_fatfs_create_file_with_text(filename_wl, str_wl);
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle));
|
||||
test_teardown_sdmmc(card);
|
||||
|
||||
/* Check that the file "sd.txt" was created on FS in SD, and has the right data */
|
||||
test_setup_sdmmc(&card);
|
||||
TEST_ASSERT_NULL(fopen(filename_wl, "r"));
|
||||
FILE* f = fopen(filename_sd, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
char buf[64];
|
||||
TEST_ASSERT_NOT_NULL(fgets(buf, sizeof(buf) - 1, f));
|
||||
TEST_ASSERT_EQUAL(0, strcmp(buf, str_sd));
|
||||
fclose(f);
|
||||
test_teardown_sdmmc(card);
|
||||
|
||||
/* Check that the file "wl.txt" was created on FS in WL, and has the right data */
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &wl_handle));
|
||||
TEST_ASSERT_NULL(fopen(filename_sd, "r"));
|
||||
f = fopen(filename_wl, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_NOT_NULL(fgets(buf, sizeof(buf) - 1, f));
|
||||
TEST_ASSERT_EQUAL(0, strcmp(buf, str_wl));
|
||||
fclose(f);
|
||||
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle));
|
||||
}
|
||||
|
||||
/*
|
||||
* In FatFs menuconfig, set CONFIG_FATFS_API_ENCODING to UTF-8 and set the
|
||||
* Codepage to CP936 (Simplified Chinese) in order to run the following tests.
|
||||
* Ensure that the text editor is UTF-8 compatible when compiling these tests.
|
||||
*/
|
||||
#if defined(CONFIG_FATFS_API_ENCODING_UTF_8) && (CONFIG_FATFS_CODEPAGE == 936)
|
||||
|
||||
static const char* test_filename_utf_8 = "/sdcard/测试文件.txt";
|
||||
|
||||
TEST_CASE("(SD) can read file using UTF-8 encoded strings", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_create_file_with_text(test_filename_utf_8, fatfs_test_hello_str_utf);
|
||||
test_fatfs_read_file_utf_8(test_filename_utf_8);
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
TEST_CASE("(SD) opendir, readdir, rewinddir, seekdir work as expected using UTF-8 encoded strings", "[fatfs][ignore]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_opendir_readdir_rewinddir_utf_8("/sdcard/目录");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
#endif // CONFIG_FATFS_API_ENCODING_UTF_8 && CONFIG_FATFS_CODEPAGE == 936
|
||||
|
||||
TEST_CASE("(SD) can get partition info", "[fatfs][sdmmc]")
|
||||
{
|
||||
sdmmc_card_t *card = NULL;
|
||||
test_setup_sdmmc(&card);
|
||||
test_fatfs_info("/sdcard", "/sdcard/test.txt");
|
||||
test_teardown_sdmmc(card);
|
||||
}
|
||||
|
||||
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3)
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include "unity.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "ff.h"
|
||||
#include "test_fatfs_common.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define SDSPI_MISO_PIN 2
|
||||
#define SDSPI_MOSI_PIN 15
|
||||
#define SDSPI_CLK_PIN 14
|
||||
#define SDSPI_CS_PIN 13
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
// Adapted for internal test board ESP-32-S3-USB-OTG-Ev-BOARD_V1.0 (with ESP32-S2-MINI-1 module)
|
||||
#define SDSPI_MISO_PIN 37
|
||||
#define SDSPI_MOSI_PIN 35
|
||||
#define SDSPI_CLK_PIN 36
|
||||
#define SDSPI_CS_PIN 34
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6
|
||||
#define SDSPI_MISO_PIN 6
|
||||
#define SDSPI_MOSI_PIN 4
|
||||
#define SDSPI_CLK_PIN 5
|
||||
#define SDSPI_CS_PIN 1
|
||||
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
|
||||
#elif CONFIG_IDF_TARGET_ESP32H2
|
||||
#define SDSPI_MISO_PIN 0
|
||||
#define SDSPI_MOSI_PIN 5
|
||||
#define SDSPI_CLK_PIN 4
|
||||
#define SDSPI_CS_PIN 1
|
||||
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
|
||||
#endif
|
||||
|
||||
#ifndef SPI_DMA_CHAN
|
||||
#define SPI_DMA_CHAN 1
|
||||
#endif //SPI_DMA_CHAN
|
||||
#define SDSPI_HOST_ID SPI2_HOST
|
||||
|
||||
|
||||
typedef struct sdspi_mem {
|
||||
size_t heap_size;
|
||||
uint32_t* buf;
|
||||
} sdspi_mem_t;
|
||||
|
||||
static const char* s_test_filename = "/sdcard/hello.txt";
|
||||
static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool write);
|
||||
|
||||
static void test_setup_sdspi(sdspi_mem_t* mem)
|
||||
{
|
||||
HEAP_SIZE_CAPTURE(mem->heap_size);
|
||||
|
||||
const size_t buf_size = 16 * 1024;
|
||||
mem->buf = (uint32_t*) calloc(1, buf_size);
|
||||
esp_fill_random(mem->buf, buf_size);
|
||||
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = SDSPI_MOSI_PIN,
|
||||
.miso_io_num = SDSPI_MISO_PIN,
|
||||
.sclk_io_num = SDSPI_CLK_PIN,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
esp_err_t err = spi_bus_initialize(SDSPI_HOST_ID, &bus_cfg, SPI_DMA_CHAN);
|
||||
TEST_ESP_OK(err);
|
||||
}
|
||||
|
||||
static void test_teardown_sdspi(sdspi_mem_t* mem)
|
||||
{
|
||||
free(mem->buf);
|
||||
spi_bus_free(SDSPI_HOST_ID);
|
||||
HEAP_SIZE_CHECK(mem->heap_size, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sdspi]")
|
||||
{
|
||||
sdspi_mem_t mem;
|
||||
size_t file_size = 1 * 1024 * 1024;
|
||||
|
||||
test_setup_sdspi(&mem);
|
||||
|
||||
sdspi_speed_test(mem.buf, 4 * 1024, file_size, true);
|
||||
sdspi_speed_test(mem.buf, 8 * 1024, file_size, true);
|
||||
sdspi_speed_test(mem.buf, 16 * 1024, file_size, true);
|
||||
|
||||
sdspi_speed_test(mem.buf, 4 * 1024, file_size, false);
|
||||
sdspi_speed_test(mem.buf, 8 * 1024, file_size, false);
|
||||
sdspi_speed_test(mem.buf, 16 * 1024, file_size, false);
|
||||
|
||||
test_teardown_sdspi(&mem);
|
||||
}
|
||||
|
||||
static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool write)
|
||||
{
|
||||
const char path[] = "/sdcard";
|
||||
sdmmc_card_t *card;
|
||||
card = NULL;
|
||||
sdspi_device_config_t device_cfg = {
|
||||
.gpio_cs = SDSPI_CS_PIN,
|
||||
.host_id = SDSPI_HOST_ID,
|
||||
.gpio_cd = SDSPI_SLOT_NO_CD,
|
||||
.gpio_wp = SDSPI_SLOT_NO_WP,
|
||||
.gpio_int = SDSPI_SLOT_NO_INT,
|
||||
};
|
||||
|
||||
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||
host.slot = SDSPI_HOST_ID;
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = write,
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 64 * 1024
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_sdspi_mount(path, &host, &device_cfg, &mount_config, &card));
|
||||
|
||||
test_fatfs_rw_speed("/sdcard/4mb.bin", buf, buf_size, file_size, write);
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card));
|
||||
}
|
||||
|
||||
TEST_CASE("(SDSPI) can get partition info", "[fatfs][sdspi]")
|
||||
{
|
||||
sdspi_mem_t mem;
|
||||
|
||||
test_setup_sdspi(&mem);
|
||||
|
||||
const char path[] = "/sdcard";
|
||||
sdmmc_card_t *card;
|
||||
card = NULL;
|
||||
sdspi_device_config_t device_cfg = {
|
||||
.gpio_cs = SDSPI_CS_PIN,
|
||||
.host_id = SDSPI_HOST_ID,
|
||||
.gpio_cd = SDSPI_SLOT_NO_CD,
|
||||
.gpio_wp = SDSPI_SLOT_NO_WP,
|
||||
.gpio_int = SDSPI_SLOT_NO_INT,
|
||||
};
|
||||
|
||||
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||
host.slot = SDSPI_HOST_ID;
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 64 * 1024
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_sdspi_mount(path, &host, &device_cfg, &mount_config, &card));
|
||||
|
||||
test_fatfs_info("/sdcard", "/sdcard/test.txt");
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card));
|
||||
|
||||
test_teardown_sdspi(&mem);
|
||||
}
|
||||
|
||||
TEST_CASE("(SDSPI) can format card", "[fatfs][sdspi]")
|
||||
{
|
||||
sdspi_mem_t mem;
|
||||
test_setup_sdspi(&mem);
|
||||
|
||||
const char path[] = "/sdcard";
|
||||
sdmmc_card_t *card;
|
||||
card = NULL;
|
||||
sdspi_device_config_t device_cfg = {
|
||||
.gpio_cs = SDSPI_CS_PIN,
|
||||
.host_id = SDSPI_HOST_ID,
|
||||
.gpio_cd = SDSPI_SLOT_NO_CD,
|
||||
.gpio_wp = SDSPI_SLOT_NO_WP,
|
||||
.gpio_int = SDSPI_SLOT_NO_INT,
|
||||
};
|
||||
|
||||
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||
host.slot = SDSPI_HOST_ID;
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 64 * 1024
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_sdspi_mount(path, &host, &device_cfg, &mount_config, &card));
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_format("/sdcard", card));
|
||||
test_fatfs_create_file_with_text(s_test_filename, fatfs_test_hello_str);
|
||||
test_fatfs_read_file(s_test_filename);
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card));
|
||||
test_teardown_sdspi(&mem);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 528k,
|
||||
|
@@ -0,0 +1,75 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.sdcard_sdmode
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'default',
|
||||
'release',
|
||||
]
|
||||
)
|
||||
def test_fatfs_sdcard_generic_sdmmc(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('[sdmmc]')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.esp32c3
|
||||
@pytest.mark.sdcard_spimode
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'default',
|
||||
'release',
|
||||
]
|
||||
)
|
||||
def test_fatfs_sdcard_generic_sdspi(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('[sdspi]')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.sdcard_sdmode
|
||||
@pytest.mark.psram
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'psram',
|
||||
]
|
||||
)
|
||||
def test_fatfs_sdcard_psram_sdmmc(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('[sdmmc]')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.sdcard_spimode
|
||||
@pytest.mark.psram
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'psram',
|
||||
]
|
||||
)
|
||||
def test_fatfs_sdcard_psram_sdspi(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('')
|
||||
dut.expect_exact('Enter test for running.')
|
||||
dut.write('[sdspi]')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
||||
@@ -0,0 +1,19 @@
|
||||
# General options for additional checks
|
||||
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
|
||||
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
|
||||
CONFIG_COMPILER_STACK_CHECK=y
|
||||
|
||||
# disable task watchdog since this app uses an interactive menu
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
# some tests verify file name encoding
|
||||
CONFIG_FATFS_API_ENCODING_UTF_8=y
|
||||
CONFIG_FATFS_CODEPAGE_936=y
|
||||
|
||||
# some of the tests verify concurrent operation of FAT partitions in
|
||||
# an SD card and in Flash, so need to use a custom partition table.
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "test_fatfs_common.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity fatfs vfs unity)
|
||||
@@ -0,0 +1,979 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <utime.h>
|
||||
#include "unity.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "test_fatfs_common.h"
|
||||
|
||||
const char* fatfs_test_hello_str = "Hello, World!\n";
|
||||
const char* fatfs_test_hello_str_utf = "世界,你好!\n";
|
||||
|
||||
void test_fatfs_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));
|
||||
}
|
||||
|
||||
void test_fatfs_create_file_with_o_creat_flag(const char* filename)
|
||||
{
|
||||
const int fd = open(filename, O_CREAT|O_WRONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
const int r = pwrite(fd, fatfs_test_hello_str, strlen(fatfs_test_hello_str), 0); //offset=0
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
void test_fatfs_open_file_with_o_creat_flag(const char* filename)
|
||||
{
|
||||
char buf[32] = { 0 };
|
||||
const int fd = open(filename, O_CREAT|O_RDONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
int r = pread(fd, buf, sizeof(buf), 0); // it is a regular read() with offset==0
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
void test_fatfs_overwrite_append(const char* filename)
|
||||
{
|
||||
/* Create new file with 'aaaa' */
|
||||
test_fatfs_create_file_with_text(filename, "aaaa");
|
||||
|
||||
/* Append 'bbbb' to file */
|
||||
FILE *f_a = fopen(filename, "a");
|
||||
TEST_ASSERT_NOT_NULL(f_a);
|
||||
TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a));
|
||||
TEST_ASSERT_EQUAL(0, fclose(f_a));
|
||||
|
||||
/* Read back 8 bytes from file, verify it's 'aaaabbbb' */
|
||||
char buf[10] = { 0 };
|
||||
FILE *f_r = fopen(filename, "r");
|
||||
TEST_ASSERT_NOT_NULL(f_r);
|
||||
TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r));
|
||||
TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8);
|
||||
|
||||
/* Be sure we're at end of file */
|
||||
TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, fclose(f_r));
|
||||
|
||||
/* Overwrite file with 'cccc' */
|
||||
test_fatfs_create_file_with_text(filename, "cccc");
|
||||
|
||||
/* Verify file now only contains 'cccc' */
|
||||
f_r = fopen(filename, "r");
|
||||
TEST_ASSERT_NOT_NULL(f_r);
|
||||
bzero(buf, sizeof(buf));
|
||||
TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4
|
||||
TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4);
|
||||
TEST_ASSERT_EQUAL(0, fclose(f_r));
|
||||
}
|
||||
|
||||
void test_fatfs_read_file(const char* filename)
|
||||
{
|
||||
FILE* f = fopen(filename, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
char buf[32] = { 0 };
|
||||
int cb = fread(buf, 1, sizeof(buf), f);
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), cb);
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
}
|
||||
|
||||
void test_fatfs_read_file_utf_8(const char* filename)
|
||||
{
|
||||
FILE* f = fopen(filename, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
char buf[64] = { 0 }; //Doubled buffer size to allow for longer UTF-8 strings
|
||||
int cb = fread(buf, 1, sizeof(buf), f);
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str_utf), cb);
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str_utf, buf));
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
}
|
||||
|
||||
void test_fatfs_pread_file(const char* filename)
|
||||
{
|
||||
char buf[32] = { 0 };
|
||||
const int fd = open(filename, O_RDONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
int r = pread(fd, buf, sizeof(buf), 0); // it is a regular read() with offset==0
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
r = pread(fd, buf, sizeof(buf), 1); // offset==1
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 1, buf));
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 1, r);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
r = pread(fd, buf, sizeof(buf), 5); // offset==5
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 5, buf));
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 5, r);
|
||||
|
||||
// regular read() should work now because pread() should not affect the current position in file
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
r = read(fd, buf, sizeof(buf)); // note that this is read() and not pread()
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
r = pread(fd, buf, sizeof(buf), 10); // offset==10
|
||||
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 10, buf));
|
||||
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 10, r);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
r = pread(fd, buf, sizeof(buf), strlen(fatfs_test_hello_str) + 1); // offset to EOF
|
||||
TEST_ASSERT_EQUAL(0, r);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
static void test_pwrite(const char *filename, off_t offset, const char *msg)
|
||||
{
|
||||
const int fd = open(filename, O_WRONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
const off_t current_pos = lseek(fd, 0, SEEK_END); // O_APPEND is not the same - jumps to the end only before write()
|
||||
|
||||
const int r = pwrite(fd, msg, strlen(msg), offset);
|
||||
TEST_ASSERT_EQUAL(strlen(msg), r);
|
||||
|
||||
TEST_ASSERT_EQUAL(current_pos, lseek(fd, 0, SEEK_CUR)); // pwrite should not move the pointer
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
static void test_file_content(const char *filename, const char *msg)
|
||||
{
|
||||
char buf[32] = { 0 };
|
||||
const int fd = open(filename, O_RDONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
int r = read(fd, buf, sizeof(buf));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, r);
|
||||
TEST_ASSERT_EQUAL(0, strcmp(msg, buf));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
void test_fatfs_pwrite_file(const char *filename)
|
||||
{
|
||||
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
|
||||
test_pwrite(filename, 0, "Hello");
|
||||
test_file_content(filename, "Hello");
|
||||
|
||||
test_pwrite(filename, strlen("Hello"), ", world!");
|
||||
test_file_content(filename, "Hello, world!");
|
||||
test_pwrite(filename, strlen("Hello, "), "Dolly");
|
||||
test_file_content(filename, "Hello, Dolly!");
|
||||
}
|
||||
|
||||
void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count)
|
||||
{
|
||||
FILE** files = calloc(files_count, sizeof(FILE*));
|
||||
for (size_t i = 0; i < files_count; ++i) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "%s_%d.txt", filename_prefix, i);
|
||||
files[i] = fopen(name, "w");
|
||||
TEST_ASSERT_NOT_NULL(files[i]);
|
||||
}
|
||||
/* close everything and clean up */
|
||||
for (size_t i = 0; i < files_count; ++i) {
|
||||
fclose(files[i]);
|
||||
}
|
||||
free(files);
|
||||
}
|
||||
|
||||
void test_fatfs_lseek(const char* filename)
|
||||
{
|
||||
FILE* f = fopen(filename, "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));
|
||||
|
||||
#ifdef CONFIG_FATFS_USE_FASTSEEK
|
||||
f = fopen(filename, "rb+");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
|
||||
TEST_ASSERT_EQUAL(18, ftell(f));
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, -4, SEEK_CUR));
|
||||
TEST_ASSERT_EQUAL(14, ftell(f));
|
||||
TEST_ASSERT_EQUAL(0, fseek(f, -14, SEEK_CUR));
|
||||
TEST_ASSERT_EQUAL(0, ftell(f));
|
||||
|
||||
TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f));
|
||||
TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1);
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void test_fatfs_truncate_file(const char* filename)
|
||||
{
|
||||
int read = 0;
|
||||
int truncated_len = 0;
|
||||
|
||||
const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
char output[sizeof(input)];
|
||||
|
||||
FILE* f = fopen(filename, "wb");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_EQUAL(strlen(input), fprintf(f, input));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
|
||||
|
||||
// Extending file beyond size is not supported
|
||||
TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
|
||||
TEST_ASSERT_EQUAL(errno, EPERM);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
|
||||
TEST_ASSERT_EQUAL(errno, EINVAL);
|
||||
|
||||
|
||||
// Truncating should succeed
|
||||
const char truncated_1[] = "ABCDEFGHIJ";
|
||||
truncated_len = strlen(truncated_1);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len));
|
||||
|
||||
f = fopen(filename, "rb");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
|
||||
memset(output, 0, sizeof(output));
|
||||
read = fread(output, 1, sizeof(output), f);
|
||||
|
||||
TEST_ASSERT_EQUAL(truncated_len, read);
|
||||
TEST_ASSERT_EQUAL_STRING_LEN(truncated_1, output, truncated_len);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
|
||||
|
||||
// Once truncated, the new file size should be the basis
|
||||
// whether truncation should succeed or not
|
||||
TEST_ASSERT_EQUAL(-1, truncate(filename, truncated_len + 1));
|
||||
TEST_ASSERT_EQUAL(EPERM, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input)));
|
||||
TEST_ASSERT_EQUAL(EPERM, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
|
||||
TEST_ASSERT_EQUAL(EPERM, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
|
||||
|
||||
// Truncating a truncated file should succeed
|
||||
const char truncated_2[] = "ABCDE";
|
||||
truncated_len = strlen(truncated_2);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len));
|
||||
|
||||
f = fopen(filename, "rb");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
|
||||
memset(output, 0, sizeof(output));
|
||||
read = fread(output, 1, sizeof(output), f);
|
||||
|
||||
TEST_ASSERT_EQUAL(truncated_len, read);
|
||||
TEST_ASSERT_EQUAL_STRING_LEN(truncated_2, output, truncated_len);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
}
|
||||
|
||||
void test_fatfs_ftruncate_file(const char* filename)
|
||||
{
|
||||
int truncated_len = 0;
|
||||
|
||||
const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
char output[sizeof(input)];
|
||||
|
||||
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
TEST_ASSERT_EQUAL(strlen(input), write(fd, input, strlen(input)));
|
||||
|
||||
// Extending file beyond size is not supported
|
||||
TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
|
||||
TEST_ASSERT_EQUAL(errno, EPERM);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
|
||||
TEST_ASSERT_EQUAL(errno, EINVAL);
|
||||
|
||||
// Truncating should succeed
|
||||
const char truncated_1[] = "ABCDEFGHIJ";
|
||||
truncated_len = strlen(truncated_1);
|
||||
TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len));
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
|
||||
// open file for reading and validate the content
|
||||
fd = open(filename, O_RDONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
memset(output, 0, sizeof(output));
|
||||
|
||||
TEST_ASSERT_EQUAL(truncated_len, read(fd, output, sizeof(output)));
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING_LEN(truncated_1, output, truncated_len);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
|
||||
// further truncate the file
|
||||
fd = open(filename, O_WRONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
// Once truncated, the new file size should be the basis
|
||||
// whether truncation should succeed or not
|
||||
TEST_ASSERT_EQUAL(-1, ftruncate(fd, truncated_len + 1));
|
||||
TEST_ASSERT_EQUAL(EPERM, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input)));
|
||||
TEST_ASSERT_EQUAL(EPERM, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
|
||||
TEST_ASSERT_EQUAL(EPERM, errno);
|
||||
|
||||
TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
|
||||
// Truncating a truncated file should succeed
|
||||
const char truncated_2[] = "ABCDE";
|
||||
truncated_len = strlen(truncated_2);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len));
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
|
||||
// open file for reading and validate the content
|
||||
fd = open(filename, O_RDONLY);
|
||||
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
||||
|
||||
memset(output, 0, sizeof(output));
|
||||
|
||||
TEST_ASSERT_EQUAL(truncated_len, read(fd, output, sizeof(output)));
|
||||
TEST_ASSERT_EQUAL_STRING_LEN(truncated_2, output, truncated_len);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
}
|
||||
|
||||
void test_fatfs_stat(const char* filename, const char* root_dir)
|
||||
{
|
||||
struct tm tm = {
|
||||
.tm_year = 2017 - 1900,
|
||||
.tm_mon = 11,
|
||||
.tm_mday = 8,
|
||||
.tm_hour = 19,
|
||||
.tm_min = 51,
|
||||
.tm_sec = 10
|
||||
};
|
||||
time_t t = mktime(&tm);
|
||||
printf("Setting time: %s", asctime(&tm));
|
||||
struct timeval now = { .tv_sec = t };
|
||||
settimeofday(&now, NULL);
|
||||
|
||||
test_fatfs_create_file_with_text(filename, "foo\n");
|
||||
|
||||
struct stat st;
|
||||
TEST_ASSERT_EQUAL(0, stat(filename, &st));
|
||||
time_t mtime = st.st_mtime;
|
||||
struct tm mtm;
|
||||
localtime_r(&mtime, &mtm);
|
||||
printf("File time: %s", asctime(&mtm));
|
||||
TEST_ASSERT(llabs(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);
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
TEST_ASSERT_EQUAL(0, stat(root_dir, &st));
|
||||
TEST_ASSERT(st.st_mode & S_IFDIR);
|
||||
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
||||
}
|
||||
|
||||
void test_fatfs_mtime_dst(const char* filename, const char* root_dir)
|
||||
{
|
||||
struct timeval tv = { 1653638041, 0 };
|
||||
settimeofday(&tv, NULL);
|
||||
setenv("TZ", "MST7MDT,M3.2.0,M11.1.0", 1);
|
||||
tzset();
|
||||
|
||||
struct tm tm;
|
||||
time_t sys_time = tv.tv_sec;
|
||||
localtime_r(&sys_time, &tm);
|
||||
printf("Setting time: %s", asctime(&tm));
|
||||
|
||||
test_fatfs_create_file_with_text(filename, "foo\n");
|
||||
|
||||
struct stat st;
|
||||
TEST_ASSERT_EQUAL(0, stat(filename, &st));
|
||||
|
||||
time_t mtime = st.st_mtime;
|
||||
struct tm mtm;
|
||||
localtime_r(&mtime, &mtm);
|
||||
printf("File time: %s", asctime(&mtm));
|
||||
|
||||
TEST_ASSERT(llabs(mtime - sys_time) < 2); // fatfs library stores time with 2 second precision
|
||||
|
||||
unsetenv("TZ");
|
||||
tzset();
|
||||
}
|
||||
|
||||
void test_fatfs_utime(const char* filename, const char* root_dir)
|
||||
{
|
||||
struct stat achieved_stat;
|
||||
struct tm desired_tm;
|
||||
struct utimbuf desired_time = {
|
||||
.actime = 0, // access time is not supported
|
||||
.modtime = 0,
|
||||
};
|
||||
time_t false_now = 0;
|
||||
memset(&desired_tm, 0, sizeof(struct tm));
|
||||
|
||||
{
|
||||
// Setting up a false actual time - used when the file is created and for modification with the current time
|
||||
desired_tm.tm_mon = 10 - 1;
|
||||
desired_tm.tm_mday = 31;
|
||||
desired_tm.tm_year = 2018 - 1900;
|
||||
desired_tm.tm_hour = 10;
|
||||
desired_tm.tm_min = 35;
|
||||
desired_tm.tm_sec = 23;
|
||||
|
||||
false_now = mktime(&desired_tm);
|
||||
|
||||
struct timeval now = { .tv_sec = false_now };
|
||||
settimeofday(&now, NULL);
|
||||
}
|
||||
test_fatfs_create_file_with_text(filename, "");
|
||||
|
||||
// 00:00:00. January 1st, 1980 - FATFS cannot handle earlier dates
|
||||
desired_tm.tm_mon = 1 - 1;
|
||||
desired_tm.tm_mday = 1;
|
||||
desired_tm.tm_year = 1980 - 1900;
|
||||
desired_tm.tm_hour = 0;
|
||||
desired_tm.tm_min = 0;
|
||||
desired_tm.tm_sec = 0;
|
||||
printf("Testing mod. time: %s", asctime(&desired_tm));
|
||||
desired_time.modtime = mktime(&desired_tm);
|
||||
TEST_ASSERT_EQUAL(0, utime(filename, &desired_time));
|
||||
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
|
||||
TEST_ASSERT_EQUAL_UINT32(desired_time.modtime, achieved_stat.st_mtime);
|
||||
|
||||
// current time
|
||||
TEST_ASSERT_EQUAL(0, utime(filename, NULL));
|
||||
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
|
||||
printf("Mod. time changed to (false actual time): %s", ctime(&achieved_stat.st_mtime));
|
||||
TEST_ASSERT_NOT_EQUAL(desired_time.modtime, achieved_stat.st_mtime);
|
||||
TEST_ASSERT(false_now - achieved_stat.st_mtime <= 2); // two seconds of tolerance are given
|
||||
|
||||
// 23:59:08. December 31st, 2037
|
||||
desired_tm.tm_mon = 12 - 1;
|
||||
desired_tm.tm_mday = 31;
|
||||
desired_tm.tm_year = 2037 - 1900;
|
||||
desired_tm.tm_hour = 23;
|
||||
desired_tm.tm_min = 59;
|
||||
desired_tm.tm_sec = 8;
|
||||
printf("Testing mod. time: %s", asctime(&desired_tm));
|
||||
desired_time.modtime = mktime(&desired_tm);
|
||||
TEST_ASSERT_EQUAL(0, utime(filename, &desired_time));
|
||||
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
|
||||
TEST_ASSERT_EQUAL_UINT32(desired_time.modtime, achieved_stat.st_mtime);
|
||||
|
||||
//WARNING: it has the Unix Millenium bug (Y2K38)
|
||||
|
||||
// 00:00:00. January 1st, 1970 - FATFS cannot handle years before 1980
|
||||
desired_tm.tm_mon = 1 - 1;
|
||||
desired_tm.tm_mday = 1;
|
||||
desired_tm.tm_year = 1970 - 1900;
|
||||
desired_tm.tm_hour = 0;
|
||||
desired_tm.tm_min = 0;
|
||||
desired_tm.tm_sec = 0;
|
||||
printf("Testing mod. time: %s", asctime(&desired_tm));
|
||||
desired_time.modtime = mktime(&desired_tm);
|
||||
TEST_ASSERT_EQUAL(-1, utime(filename, &desired_time));
|
||||
TEST_ASSERT_EQUAL(EINVAL, errno);
|
||||
}
|
||||
|
||||
void test_fatfs_unlink(const char* filename)
|
||||
{
|
||||
test_fatfs_create_file_with_text(filename, "unlink\n");
|
||||
|
||||
TEST_ASSERT_EQUAL(0, unlink(filename));
|
||||
|
||||
TEST_ASSERT_NULL(fopen(filename, "r"));
|
||||
}
|
||||
|
||||
void test_fatfs_link_rename(const char* filename_prefix)
|
||||
{
|
||||
char name_copy[64];
|
||||
char name_dst[64];
|
||||
char name_src[64];
|
||||
snprintf(name_copy, sizeof(name_copy), "%s_cpy.txt", filename_prefix);
|
||||
snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix);
|
||||
snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix);
|
||||
|
||||
unlink(name_copy);
|
||||
unlink(name_dst);
|
||||
unlink(name_src);
|
||||
|
||||
FILE* f = fopen(name_src, "w+");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
const 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(name_src, name_copy));
|
||||
FILE* fcopy = fopen(name_copy, "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(name_copy, name_dst));
|
||||
TEST_ASSERT_NULL(fopen(name_copy, "r"));
|
||||
FILE* fdst = fopen(name_dst, "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));
|
||||
}
|
||||
|
||||
void test_fatfs_mkdir_rmdir(const char* filename_prefix)
|
||||
{
|
||||
char name_dir1[64];
|
||||
char name_dir2[64];
|
||||
char name_dir2_file[64];
|
||||
snprintf(name_dir1, sizeof(name_dir1), "%s1", filename_prefix);
|
||||
snprintf(name_dir2, sizeof(name_dir2), "%s2", filename_prefix);
|
||||
snprintf(name_dir2_file, sizeof(name_dir2_file), "%s2/1.txt", filename_prefix);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, mkdir(name_dir1, 0755));
|
||||
struct stat st;
|
||||
TEST_ASSERT_EQUAL(0, stat(name_dir1, &st));
|
||||
TEST_ASSERT_TRUE(st.st_mode & S_IFDIR);
|
||||
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
||||
TEST_ASSERT_EQUAL(0, rmdir(name_dir1));
|
||||
TEST_ASSERT_EQUAL(-1, stat(name_dir1, &st));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, mkdir(name_dir2, 0755));
|
||||
test_fatfs_create_file_with_text(name_dir2_file, "foo\n");
|
||||
TEST_ASSERT_EQUAL(0, stat(name_dir2, &st));
|
||||
TEST_ASSERT_TRUE(st.st_mode & S_IFDIR);
|
||||
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
||||
TEST_ASSERT_EQUAL(0, stat(name_dir2_file, &st));
|
||||
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
|
||||
TEST_ASSERT_TRUE(st.st_mode & S_IFREG);
|
||||
TEST_ASSERT_EQUAL(-1, rmdir(name_dir2));
|
||||
TEST_ASSERT_EQUAL(0, unlink(name_dir2_file));
|
||||
TEST_ASSERT_EQUAL(0, rmdir(name_dir2));
|
||||
}
|
||||
|
||||
void test_fatfs_can_opendir(const char* path)
|
||||
{
|
||||
char name_dir_file[64];
|
||||
const char * file_name = "test_opd.txt";
|
||||
snprintf(name_dir_file, sizeof(name_dir_file), "%s/%s", path, file_name);
|
||||
unlink(name_dir_file);
|
||||
test_fatfs_create_file_with_text(name_dir_file, "test_opendir\n");
|
||||
DIR* dir = opendir(path);
|
||||
TEST_ASSERT_NOT_NULL(dir);
|
||||
bool found = false;
|
||||
while (true) {
|
||||
struct dirent* de = readdir(dir);
|
||||
if (!de) {
|
||||
break;
|
||||
}
|
||||
if (strcasecmp(de->d_name, file_name) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_TRUE(found);
|
||||
TEST_ASSERT_EQUAL(0, closedir(dir));
|
||||
unlink(name_dir_file);
|
||||
}
|
||||
|
||||
void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix)
|
||||
{
|
||||
char name_dir_inner_file[64];
|
||||
char name_dir_inner[64];
|
||||
char name_dir_file3[64];
|
||||
char name_dir_file2[64];
|
||||
char name_dir_file1[64];
|
||||
|
||||
snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix);
|
||||
snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix);
|
||||
snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix);
|
||||
snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix);
|
||||
snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix);
|
||||
|
||||
unlink(name_dir_inner_file);
|
||||
rmdir(name_dir_inner);
|
||||
unlink(name_dir_file1);
|
||||
unlink(name_dir_file2);
|
||||
unlink(name_dir_file3);
|
||||
rmdir(dir_prefix);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755));
|
||||
test_fatfs_create_file_with_text(name_dir_file1, "1\n");
|
||||
test_fatfs_create_file_with_text(name_dir_file2, "2\n");
|
||||
test_fatfs_create_file_with_text(name_dir_file3, "\01\02\03");
|
||||
TEST_ASSERT_EQUAL(0, mkdir(name_dir_inner, 0755));
|
||||
test_fatfs_create_file_with_text(name_dir_inner_file, "3\n");
|
||||
|
||||
DIR* dir = opendir(dir_prefix);
|
||||
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));
|
||||
}
|
||||
|
||||
void test_fatfs_opendir_readdir_rewinddir_utf_8(const char* dir_prefix)
|
||||
{
|
||||
char name_dir_inner_file[64];
|
||||
char name_dir_inner[64];
|
||||
char name_dir_file3[64];
|
||||
char name_dir_file2[64];
|
||||
char name_dir_file1[64];
|
||||
|
||||
snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/内部目录/内部文件.txt", dir_prefix);
|
||||
snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/内部目录", dir_prefix);
|
||||
snprintf(name_dir_file3, sizeof(name_dir_file3), "%s/文件三.bin", dir_prefix);
|
||||
snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/文件二.txt", dir_prefix);
|
||||
snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/文件一.txt", dir_prefix);
|
||||
|
||||
unlink(name_dir_inner_file);
|
||||
rmdir(name_dir_inner);
|
||||
unlink(name_dir_file1);
|
||||
unlink(name_dir_file2);
|
||||
unlink(name_dir_file3);
|
||||
rmdir(dir_prefix);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755));
|
||||
test_fatfs_create_file_with_text(name_dir_file1, "一号\n");
|
||||
test_fatfs_create_file_with_text(name_dir_file2, "二号\n");
|
||||
test_fatfs_create_file_with_text(name_dir_file3, "\0一\0二\0三");
|
||||
TEST_ASSERT_EQUAL(0, mkdir(name_dir_inner, 0755));
|
||||
test_fatfs_create_file_with_text(name_dir_inner_file, "三号\n");
|
||||
|
||||
DIR* dir = opendir(dir_prefix);
|
||||
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, "文件一.txt") == 0) {
|
||||
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
||||
names[count] = "文件一.txt";
|
||||
++count;
|
||||
} else if (strcasecmp(de->d_name, "文件二.txt") == 0) {
|
||||
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
||||
names[count] = "文件二.txt";
|
||||
++count;
|
||||
} else if (strcasecmp(de->d_name, "内部目录") == 0) {
|
||||
TEST_ASSERT_TRUE(de->d_type == DT_DIR);
|
||||
names[count] = "内部目录";
|
||||
++count;
|
||||
} else if (strcasecmp(de->d_name, "文件三.bin") == 0) {
|
||||
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
||||
names[count] = "文件三.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));
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char* filename;
|
||||
bool write;
|
||||
size_t word_count;
|
||||
unsigned seed;
|
||||
SemaphoreHandle_t done;
|
||||
esp_err_t 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) {
|
||||
unsigned val = rand();
|
||||
if (args->write) {
|
||||
int cnt = fwrite(&val, sizeof(val), 1, f);
|
||||
if (cnt != 1) {
|
||||
printf("E(w): i=%d, cnt=%d val=0x08%x\n", i, cnt, val);
|
||||
args->result = ESP_FAIL;
|
||||
goto close;
|
||||
}
|
||||
} else {
|
||||
unsigned rval;
|
||||
int cnt = fread(&rval, sizeof(rval), 1, f);
|
||||
if (cnt != 1 || rval != val) {
|
||||
printf("E(r): i=%d, cnt=%d rval=0x08%x val=0x08%x\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);
|
||||
}
|
||||
|
||||
void test_fatfs_concurrent(const char* filename_prefix)
|
||||
{
|
||||
char names[4][64];
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
snprintf(names[i], sizeof(names[i]), "%s%d", filename_prefix, i + 1);
|
||||
unlink(names[i]);
|
||||
}
|
||||
|
||||
read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(names[0], 1);
|
||||
read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(names[1], 2);
|
||||
|
||||
printf("writing f1 and f2\n");
|
||||
|
||||
const int cpuid_0 = 0;
|
||||
const int cpuid_1 = portNUM_PROCESSORS - 1;
|
||||
const int stack_size = 4096;
|
||||
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
|
||||
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_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(names[2], 3);
|
||||
read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(names[3], 4);
|
||||
|
||||
printf("reading f1 and f2, writing f3 and f4\n");
|
||||
|
||||
xTaskCreatePinnedToCore(&read_write_task, "rw3", stack_size, &args3, 3, NULL, cpuid_1);
|
||||
xTaskCreatePinnedToCore(&read_write_task, "rw4", stack_size, &args4, 3, NULL, cpuid_0);
|
||||
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
|
||||
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_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);
|
||||
|
||||
vSemaphoreDelete(args1.done);
|
||||
vSemaphoreDelete(args2.done);
|
||||
vSemaphoreDelete(args3.done);
|
||||
vSemaphoreDelete(args4.done);
|
||||
}
|
||||
|
||||
void test_leading_spaces(void){
|
||||
// fatfs should ignore leading and trailing whitespaces
|
||||
// and files "/spiflash/ thelongfile.txt " and "/spiflash/thelongfile.txt" should be equivalent
|
||||
// this feature is currently not implemented
|
||||
FILE* f = fopen( "/spiflash/ thelongfile.txt ", "wb");
|
||||
fclose(f);
|
||||
TEST_ASSERT_NULL(fopen("/spiflash/thelongfile.txt", "r"));
|
||||
}
|
||||
|
||||
void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool is_write)
|
||||
{
|
||||
const size_t buf_count = file_size / buf_size;
|
||||
|
||||
FILE* f = fopen(filename, (is_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 (is_write) {
|
||||
TEST_ASSERT_EQUAL(buf_size, write(fileno(f), buf, buf_size));
|
||||
} else {
|
||||
if (read(fileno(f), buf, buf_size) != buf_size) {
|
||||
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));
|
||||
|
||||
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",
|
||||
(is_write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3,
|
||||
file_size / (1024.0f * 1024.0f * t_s));
|
||||
}
|
||||
|
||||
void test_fatfs_info(const char* base_path, const char* filepath)
|
||||
{
|
||||
// Empty FS
|
||||
uint64_t total_bytes = 0;
|
||||
uint64_t free_bytes = 0;
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_fat_info(base_path, &total_bytes, &free_bytes));
|
||||
ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes=%llu", total_bytes, free_bytes);
|
||||
TEST_ASSERT_NOT_EQUAL(0, total_bytes);
|
||||
|
||||
// FS with a file
|
||||
FILE* f = fopen(filepath, "wb");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_TRUE(fputs(fatfs_test_hello_str, f) != EOF);
|
||||
TEST_ASSERT_EQUAL(0, fclose(f));
|
||||
|
||||
uint64_t free_bytes_new = 0;
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_fat_info(base_path, &total_bytes, &free_bytes_new));
|
||||
ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes_new=%llu", total_bytes, free_bytes_new);
|
||||
TEST_ASSERT_NOT_EQUAL(free_bytes, free_bytes_new);
|
||||
|
||||
// File removed
|
||||
TEST_ASSERT_EQUAL(0, remove(filepath));
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_fat_info(base_path, &total_bytes, &free_bytes_new));
|
||||
ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes_after_delete=%llu", total_bytes, free_bytes_new);
|
||||
TEST_ASSERT_EQUAL(free_bytes, free_bytes_new);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file test_fatfs_common.h
|
||||
* @brief Common routines for FAT-on-SDMMC and FAT-on-WL tests
|
||||
*/
|
||||
|
||||
#define HEAP_SIZE_CAPTURE(heap_size) \
|
||||
heap_size = esp_get_free_heap_size();
|
||||
|
||||
#define HEAP_SIZE_CHECK(heap_size, 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)
|
||||
|
||||
extern const char* fatfs_test_hello_str;
|
||||
extern const char* fatfs_test_hello_str_utf;
|
||||
|
||||
void test_fatfs_create_file_with_text(const char* name, const char* text);
|
||||
|
||||
void test_fatfs_create_file_with_o_creat_flag(const char* filename);
|
||||
|
||||
void test_fatfs_open_file_with_o_creat_flag(const char* filename);
|
||||
|
||||
void test_fatfs_overwrite_append(const char* filename);
|
||||
|
||||
void test_fatfs_read_file(const char* filename);
|
||||
|
||||
void test_fatfs_read_file_utf_8(const char* filename);
|
||||
|
||||
void test_fatfs_pread_file(const char* filename);
|
||||
|
||||
void test_fatfs_pwrite_file(const char* filename);
|
||||
|
||||
void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count);
|
||||
|
||||
void test_fatfs_lseek(const char* filename);
|
||||
|
||||
void test_fatfs_truncate_file(const char* path);
|
||||
|
||||
void test_fatfs_ftruncate_file(const char* path);
|
||||
|
||||
void test_fatfs_stat(const char* filename, const char* root_dir);
|
||||
|
||||
void test_fatfs_mtime_dst(const char* filename, const char* root_dir);
|
||||
|
||||
void test_fatfs_utime(const char* filename, const char* root_dir);
|
||||
|
||||
void test_fatfs_unlink(const char* filename);
|
||||
|
||||
void test_fatfs_link_rename(const char* filename_prefix);
|
||||
|
||||
void test_fatfs_concurrent(const char* filename_prefix);
|
||||
|
||||
void test_fatfs_mkdir_rmdir(const char* filename_prefix);
|
||||
|
||||
void test_fatfs_can_opendir(const char* path);
|
||||
|
||||
void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix);
|
||||
|
||||
void test_fatfs_opendir_readdir_rewinddir_utf_8(const char* dir_prefix);
|
||||
|
||||
void test_leading_spaces(void);
|
||||
|
||||
void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool write);
|
||||
|
||||
void test_fatfs_info(const char* base_path, const char* filepath);
|
||||
@@ -0,0 +1,106 @@
|
||||
ifndef COMPONENT
|
||||
COMPONENT := fatfs
|
||||
endif
|
||||
|
||||
COMPONENT_LIB := lib$(COMPONENT).a
|
||||
TEST_PROGRAM := test_$(COMPONENT)
|
||||
|
||||
STUBS_LIB_DIR := ../../../components/spi_flash/sim/stubs
|
||||
STUBS_LIB_BUILD_DIR := $(STUBS_LIB_DIR)/build
|
||||
STUBS_LIB := libstubs.a
|
||||
|
||||
SPI_FLASH_SIM_DIR := ../../../components/spi_flash/sim
|
||||
SPI_FLASH_SIM_BUILD_DIR := $(SPI_FLASH_SIM_DIR)/build
|
||||
SPI_FLASH_SIM_LIB := libspi_flash.a
|
||||
|
||||
WEAR_LEVELLING_DIR := ../../../components/wear_levelling/test_wl_host
|
||||
WEAR_LEVELLING_BUILD_DIR := $(WEAR_LEVELLING_DIR)/build
|
||||
WEAR_LEVELLING_LIB := libwl.a
|
||||
|
||||
include Makefile.files
|
||||
|
||||
all: test
|
||||
|
||||
ifndef SDKCONFIG
|
||||
SDKCONFIG_DIR := $(dir $(realpath sdkconfig/sdkconfig.h))
|
||||
SDKCONFIG := $(SDKCONFIG_DIR)sdkconfig.h
|
||||
else
|
||||
SDKCONFIG_DIR := $(dir $(realpath $(SDKCONFIG)))
|
||||
endif
|
||||
|
||||
INCLUDE_FLAGS := $(addprefix -I, $(INCLUDE_DIRS) $(SDKCONFIG_DIR) ../../../tools/catch)
|
||||
|
||||
CPPFLAGS += $(INCLUDE_FLAGS) -g -m32
|
||||
CXXFLAGS += $(INCLUDE_FLAGS) -std=c++11 -g -m32
|
||||
|
||||
# Build libraries that this component is dependent on
|
||||
$(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB): force
|
||||
$(MAKE) -C $(STUBS_LIB_DIR) lib SDKCONFIG=$(SDKCONFIG)
|
||||
|
||||
$(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB): force
|
||||
$(MAKE) -C $(SPI_FLASH_SIM_DIR) lib SDKCONFIG=$(SDKCONFIG)
|
||||
|
||||
$(WEAR_LEVELLING_BUILD_DIR)/$(WEAR_LEVELLING_LIB): force
|
||||
$(MAKE) -C $(WEAR_LEVELLING_DIR) lib SDKCONFIG=$(SDKCONFIG)
|
||||
|
||||
# Create target for building this component as a library
|
||||
CFILES := $(filter %.c, $(SOURCE_FILES))
|
||||
CPPFILES := $(filter %.cpp, $(SOURCE_FILES))
|
||||
|
||||
CTARGET = ${2}/$(patsubst %.c,%.o,$(notdir ${1}))
|
||||
CPPTARGET = ${2}/$(patsubst %.cpp,%.o,$(notdir ${1}))
|
||||
|
||||
ifndef BUILD_DIR
|
||||
BUILD_DIR := build
|
||||
endif
|
||||
|
||||
OBJ_FILES := $(addprefix $(BUILD_DIR)/, $(filter %.o, $(notdir $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o))))
|
||||
|
||||
define COMPILE_C
|
||||
$(call CTARGET, ${1}, $(BUILD_DIR)) : ${1} $(SDKCONFIG)
|
||||
mkdir -p $(BUILD_DIR)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $(call CTARGET, ${1}, $(BUILD_DIR)) ${1}
|
||||
endef
|
||||
|
||||
define COMPILE_CPP
|
||||
$(call CPPTARGET, ${1}, $(BUILD_DIR)) : ${1} $(SDKCONFIG)
|
||||
mkdir -p $(BUILD_DIR)
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $(call CPPTARGET, ${1}, $(BUILD_DIR)) ${1}
|
||||
endef
|
||||
|
||||
$(BUILD_DIR)/$(COMPONENT_LIB): $(OBJ_FILES) $(SDKCONFIG)
|
||||
mkdir -p $(BUILD_DIR)
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
lib: $(BUILD_DIR)/$(COMPONENT_LIB)
|
||||
|
||||
$(foreach cfile, $(CFILES), $(eval $(call COMPILE_C, $(cfile))))
|
||||
$(foreach cxxfile, $(CPPFILES), $(eval $(call COMPILE_CPP, $(cxxfile))))
|
||||
|
||||
# Create target for building this component as a test
|
||||
TEST_SOURCE_FILES = \
|
||||
test_fatfs.cpp \
|
||||
main.cpp \
|
||||
|
||||
TEST_OBJ_FILES = $(filter %.o, $(TEST_SOURCE_FILES:.cpp=.o) $(TEST_SOURCE_FILES:.c=.o))
|
||||
|
||||
$(TEST_PROGRAM): lib $(TEST_OBJ_FILES) $(WEAR_LEVELLING_BUILD_DIR)/$(WEAR_LEVELLING_LIB) $(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB) $(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB) partition_table.bin $(SDKCONFIG)
|
||||
g++ $(LDFLAGS) $(CXXFLAGS) -o $@ $(TEST_OBJ_FILES) -L$(BUILD_DIR) -l:$(COMPONENT_LIB) -L$(WEAR_LEVELLING_BUILD_DIR) -l:$(WEAR_LEVELLING_LIB) -L$(SPI_FLASH_SIM_BUILD_DIR) -l:$(SPI_FLASH_SIM_LIB) -L$(STUBS_LIB_BUILD_DIR) -l:$(STUBS_LIB)
|
||||
|
||||
test: $(TEST_PROGRAM)
|
||||
./$(TEST_PROGRAM)
|
||||
|
||||
# Create other necessary targets
|
||||
partition_table.bin: partition_table.csv
|
||||
python ../../../components/partition_table/gen_esp32part.py --verify $< $@
|
||||
|
||||
force:
|
||||
|
||||
# Create target to cleanup files
|
||||
clean:
|
||||
$(MAKE) -C $(STUBS_LIB_DIR) clean
|
||||
$(MAKE) -C $(SPI_FLASH_SIM_DIR) clean
|
||||
$(MAKE) -C $(WEAR_LEVELLING_DIR) clean
|
||||
rm -f $(OBJ_FILES) $(TEST_OBJ_FILES) $(TEST_PROGRAM) $(COMPONENT_LIB) partition_table.bin
|
||||
|
||||
.PHONY: all lib test clean force
|
||||
@@ -0,0 +1,44 @@
|
||||
SOURCE_FILES := \
|
||||
$(addprefix ../src/, \
|
||||
ff.c \
|
||||
ffunicode.c \
|
||||
) \
|
||||
$(addprefix ../diskio/,\
|
||||
diskio.c \
|
||||
diskio_wl.c \
|
||||
) \
|
||||
../port/linux/ffsystem.c
|
||||
|
||||
INCLUDE_DIRS := \
|
||||
. \
|
||||
../diskio \
|
||||
../src \
|
||||
$(addprefix ../../spi_flash/sim/stubs/, \
|
||||
app_update/include \
|
||||
driver/include \
|
||||
freertos/include \
|
||||
newlib/include \
|
||||
sdmmc/include \
|
||||
vfs/include \
|
||||
) \
|
||||
$(addprefix ../../../components/, \
|
||||
esp_rom/include \
|
||||
esp_hw_support/include \
|
||||
esp_hw_support/include/soc \
|
||||
esp_system/include \
|
||||
log/include \
|
||||
xtensa/include \
|
||||
xtensa/esp32/include \
|
||||
soc/esp32/include \
|
||||
heap/include \
|
||||
soc/include \
|
||||
esp32/include \
|
||||
esp_common/include \
|
||||
bootloader_support/include \
|
||||
bootloader_support/bootloader_flash/include \
|
||||
app_update/include \
|
||||
hal/include \
|
||||
spi_flash/include \
|
||||
wear_levelling/include \
|
||||
esp_partition/include \
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
include $(COMPONENT_PATH)/Makefile.files
|
||||
|
||||
COMPONENT_OWNBUILDTARGET := 1
|
||||
COMPONENT_OWNCLEANTARGET := 1
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := $(INCLUDE_DIRS)
|
||||
|
||||
.PHONY: build
|
||||
build: $(SDKCONFIG_HEADER)
|
||||
$(MAKE) -C $(COMPONENT_PATH) lib SDKCONFIG=$(SDKCONFIG_HEADER) BUILD_DIR=$(COMPONENT_BUILD_DIR) COMPONENT=$(COMPONENT_NAME)
|
||||
|
||||
CLEAN_FILES := component_project_vars.mk
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(summary) RM $(CLEAN_FILES)
|
||||
rm -f $(CLEAN_FILES)
|
||||
$(MAKE) -C $(COMPONENT_PATH) clean SDKCONFIG=$(SDKCONFIG_HEADER) BUILD_DIR=$(COMPONENT_BUILD_DIR) COMPONENT=$(COMPONENT_NAME)
|
||||
@@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 1M,
|
||||
|
@@ -0,0 +1,94 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ff.h"
|
||||
#include "esp_partition.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_wl.h"
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
extern "C" void _spi_flash_init(const char* chip_size, size_t block_size, size_t sector_size, size_t page_size, const char* partition_bin);
|
||||
|
||||
TEST_CASE("create volume, open file, write and read back data", "[fatfs]")
|
||||
{
|
||||
_spi_flash_init(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin");
|
||||
|
||||
FRESULT fr_result;
|
||||
BYTE pdrv;
|
||||
FATFS fs;
|
||||
FIL file;
|
||||
UINT bw;
|
||||
|
||||
esp_err_t esp_result;
|
||||
|
||||
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage");
|
||||
|
||||
// Mount wear-levelled partition
|
||||
wl_handle_t wl_handle;
|
||||
esp_result = wl_mount(partition, &wl_handle);
|
||||
REQUIRE(esp_result == ESP_OK);
|
||||
|
||||
// Get a physical drive
|
||||
esp_result = ff_diskio_get_drive(&pdrv);
|
||||
REQUIRE(esp_result == ESP_OK);
|
||||
|
||||
// Register physical drive as wear-levelled partition
|
||||
esp_result = ff_diskio_register_wl_partition(pdrv, wl_handle);
|
||||
|
||||
// Create FAT volume on the entire disk
|
||||
LBA_t part_list[] = {100, 0, 0, 0};
|
||||
BYTE work_area[FF_MAX_SS];
|
||||
|
||||
fr_result = f_fdisk(pdrv, part_list, work_area);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0};
|
||||
fr_result = f_mkfs("", &opt, work_area, sizeof(work_area)); // Use default volume
|
||||
|
||||
// Mount the volume
|
||||
fr_result = f_mount(&fs, "", 0);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
|
||||
// Open, write and read data
|
||||
fr_result = f_open(&file, "test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
|
||||
// Generate data
|
||||
uint32_t data_size = 100000;
|
||||
|
||||
char *data = (char*) malloc(data_size);
|
||||
char *read = (char*) malloc(data_size);
|
||||
|
||||
for(uint32_t i = 0; i < data_size; i += sizeof(i))
|
||||
{
|
||||
*((uint32_t*)(data + i)) = i;
|
||||
}
|
||||
|
||||
// Write generated data
|
||||
fr_result = f_write(&file, data, data_size, &bw);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
REQUIRE(bw == data_size);
|
||||
|
||||
// Move to beginning of file
|
||||
fr_result = f_lseek(&file, 0);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
|
||||
// Read written data
|
||||
fr_result = f_read(&file, read, data_size, &bw);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
REQUIRE(bw == data_size);
|
||||
|
||||
REQUIRE(memcmp(data, read, data_size) == 0);
|
||||
|
||||
// Close file
|
||||
fr_result = f_close(&file);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
|
||||
// Unmount default volume
|
||||
fr_result = f_mount(0, "", 0);
|
||||
REQUIRE(fr_result == FR_OK);
|
||||
|
||||
free(read);
|
||||
free(data);
|
||||
}
|
||||
Executable
+535
@@ -0,0 +1,535 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
from subprocess import STDOUT, check_output
|
||||
|
||||
from test_utils import CFG, fill_sector, generate_test_dir_1, generate_test_dir_2
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import fatfsgen # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.boot_sector import BootSector # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.cluster import Cluster # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.entry import Entry # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.exceptions import InconsistentFATAttributes # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.exceptions import NotInitialized # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.exceptions import TooLongNameException # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.exceptions import WriteDirectoryException # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.exceptions import LowerCaseException, NoFreeClusterException # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.utils import right_strip_string # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.utils import FAT12, read_filesystem # noqa E402 # pylint: disable=C0413
|
||||
|
||||
|
||||
class FatFSGen(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
os.makedirs('output_data')
|
||||
os.makedirs('test_dir')
|
||||
generate_test_dir_1()
|
||||
generate_test_dir_2()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree('output_data')
|
||||
shutil.rmtree('test_dir')
|
||||
|
||||
if os.path.exists('fatfs_image.img'):
|
||||
os.remove('fatfs_image.img')
|
||||
|
||||
def test_empty_file_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('TESTFILE')
|
||||
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000:0x200c], b'TESTFILE \x20') # check entry name and type
|
||||
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
|
||||
|
||||
def test_directory_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000:0x200c], b'TESTFOLD \x10') # check entry name and type
|
||||
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
|
||||
self.assertEqual(file_system[0x6000:0x600c], b'. \x10') # reference to itself
|
||||
self.assertEqual(file_system[0x6020:0x602c], b'.. \x10') # reference to parent
|
||||
|
||||
def test_empty_file_with_extension_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('TESTF', extension='TXT')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000:0x200c], b'TESTF TXT\x20') # check entry name and type
|
||||
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
|
||||
|
||||
def test_write_to_file_with_extension_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=b'testcontent')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000:0x200c], b'WRITEF TXT\x20') # check entry name and type
|
||||
self.assertEqual(file_system[0x201a:0x2020], b'\x02\x00\x0b\x00\x00\x00') # check size and cluster ref
|
||||
self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
|
||||
self.assertEqual(file_system[0x6000:0x600f], b'testcontent\x00\x00\x00\x00') # check file content
|
||||
|
||||
def test_write_to_file_in_folder_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'WRITEF.TXT'], content=b'testcontent')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000:0x200c], b'TESTFOLD \x10')
|
||||
self.assertEqual(
|
||||
file_system[0x1000:0x1010],
|
||||
b'\xf8\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'WRITEF TXT\x20\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x605a:0x6060], b'\x03\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7000:0x700b], b'testcontent') # check file content
|
||||
|
||||
def test_cluster_setting_values(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('TESTFIL1')
|
||||
fatfs.create_file('TESTFIL2')
|
||||
fatfs.create_file('TESTFIL3')
|
||||
fatfs.create_file('TESTFIL4')
|
||||
fatfs.create_file('TESTFIL5')
|
||||
fatfs.fat.clusters[2].set_in_fat(1000)
|
||||
fatfs.fat.clusters[3].set_in_fat(4)
|
||||
fatfs.fat.clusters[4].set_in_fat(5)
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(
|
||||
file_system[0x1000:0x1010],
|
||||
b'\xf8\xff\xff\xe8\x43\x00\x05\xf0\xff\xff\x0f\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_full_sector_file(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x7000], CFG['sector_size'] * b'a')
|
||||
|
||||
def test_file_chaining(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\x03\xf0\xff\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7000: 0x8000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
|
||||
|
||||
def test_full_sector_folder(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
|
||||
fill_sector(fatfs)
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000: 0x10d0],
|
||||
b'\xf8\xff\xff\x82\xf0\xff' + 192 * b'\xff' + 10 * b'\x00')
|
||||
self.assertEqual(file_system[0x85000:0x85005], b'later')
|
||||
self.assertEqual(file_system[0x86000:0x86010], b'A126 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x86020:0x86030], b'A127 \x00\x00\x00\x00')
|
||||
|
||||
def test_write_to_folder_in_folder_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD'])
|
||||
self.assertRaises(WriteDirectoryException, fatfs.write_content, path_from_root=['TESTFOLD', 'TESTFOLL'],
|
||||
content=b'testcontent')
|
||||
|
||||
def test_write_non_existing_file_in_folder_sn_fat12(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
self.assertRaises(FileNotFoundError, fatfs.write_content, path_from_root=['TESTFOLD', 'AHOJ'],
|
||||
content=b'testcontent')
|
||||
|
||||
@staticmethod
|
||||
def create_too_many_files() -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
for i in range(2 * CFG['sector_size'] // CFG['entry_size']):
|
||||
fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD'])
|
||||
|
||||
def test_too_many_files(self) -> None:
|
||||
self.assertRaises(NoFreeClusterException, self.create_too_many_files)
|
||||
|
||||
def test_full_two_sectors_folder(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(size=2 * 1024 * 1024)
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
|
||||
for i in range(2 * CFG['sector_size'] // CFG['entry_size']):
|
||||
fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A253'], content=b'later')
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A255'], content=b'last')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x105000:0x105010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x108000:0x108010], b'last\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_lower_case_dir_short_names(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
self.assertRaises(LowerCaseException, fatfs.create_directory, 'testfold')
|
||||
|
||||
def test_lower_case_file_short_names(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
self.assertRaises(LowerCaseException, fatfs.create_file, 'newfile')
|
||||
|
||||
def test_too_long_name_dir_short_names(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
self.assertRaises(TooLongNameException, fatfs.create_directory, 'TOOLONGNAME')
|
||||
|
||||
def test_fatfs16_detection(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(size=16 * 1024 * 1024)
|
||||
self.assertEqual(fatfs.state.boot_sector_state.fatfs_type, 16)
|
||||
|
||||
def test_fatfs32_detection(self) -> None:
|
||||
self.assertRaises(NotImplementedError, fatfsgen.FATFS, size=256 * 1024 * 1024)
|
||||
|
||||
def test_deep_structure(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD'])
|
||||
fatfs.create_directory('TESTFOLO', path_from_root=['TESTFOLD', 'TESTFOLL'])
|
||||
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD', 'TESTFOLL', 'TESTFOLO'])
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'TESTFOLL', 'TESTFOLO', 'WRITEF.TXT'], content=b'later')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_same_name_deep_structure(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD'])
|
||||
fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD', 'TESTFOLD'])
|
||||
fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD', 'TESTFOLD', 'TESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'TESTFOLD', 'TESTFOLD', 'WRITEF.TXT'], content=b'later')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000:0x2010], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2010:0x2020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'WRITEF TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_e2e_deep_folder_into_image(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.generate(CFG['test_dir'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6070:0x6080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_e2e_deep_folder_into_image_ext(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.generate(CFG['test_dir2'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2020:0x2030], b'TESTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7000:0x7010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILETXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xb000:0xb009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')
|
||||
|
||||
def test_empty_fat16(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000:0x1007], b'\xf8\xff\xff\xff\x00\x00\x00')
|
||||
|
||||
def test_simple_fat16(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000:0x1007], b'\xf8\xff\xff\xff\xff\xff\x00')
|
||||
|
||||
def test_chaining_fat16(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000: 0xa000], b'a' + (CFG['sector_size'] - 1) * b'\x00')
|
||||
|
||||
def test_full_sector_folder_fat16(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
|
||||
fill_sector(fatfs)
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x1000: 0x1110],
|
||||
b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00')
|
||||
self.assertEqual(file_system[0x87000:0x87005], b'later')
|
||||
self.assertEqual(file_system[0x88000:0x88010], b'A126 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x88020:0x88030], b'A127 \x00\x00\x00\x00')
|
||||
|
||||
def test_empty_lfn_short_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLO', extension='TXT')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2019], b'HELLO TXT \x18\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!')
|
||||
|
||||
def test_lfn_short_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLO', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['HELLO.TXT'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'HELLO TXT \x18\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2010: 0x2020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
|
||||
|
||||
def test_lfn_empty_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLOHELLOHELLO', extension='TXT')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xadt\x00')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~\x01TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_lfn_plain_name(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLOHELLOHELLO', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['HELLOHELLOHELLO.TXT'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xadt\x00')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~\x01TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
|
||||
|
||||
def test_lfn_plain_name_no_ext(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_file('HELLOHELLOHELLO')
|
||||
fatfs.write_content(path_from_root=['HELLOHELLOHELLO'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bl\x00o\x00\x00\x00\xff\xff\xff\xff\x0f\x00P\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00Ph\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~\x01 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
|
||||
|
||||
def test_lfn_short_entry_exception(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
self.assertRaises(LowerCaseException, fatfs.create_file, 'hello', extension='txt')
|
||||
|
||||
def test_lfn_nested_empty(self) -> None:
|
||||
fatfs = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('VERYLONGTESTFOLD')
|
||||
fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~\x01 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'HELLO TXT \x18\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00')
|
||||
|
||||
def test_lfn_nested_long_empty(self) -> None:
|
||||
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('verylongtestfold')
|
||||
fatfs.create_file('hellohellohello', extension='txt', path_from_root=['verylongtestfold'])
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\n\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\no\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'verylo~\x01 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xcft\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060],
|
||||
b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
|
||||
def test_lfn_nested_text(self) -> None:
|
||||
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('VERYLONGTESTFOLD')
|
||||
fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLO.TXT'], content=b'this is a test')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff')
|
||||
self.assertEqual(file_system[0x2012: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~\x01 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'HELLO TXT \x18\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x0e\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')
|
||||
|
||||
def test_boundary_clusters12(self) -> None:
|
||||
output: bytes = check_output(['python', '../fatfsgen.py', '--partition_size', '16732160', 'test_dir'],
|
||||
stderr=STDOUT)
|
||||
self.assertEqual(
|
||||
output,
|
||||
b'WARNING: It is not recommended to create FATFS with bounding count of clusters: 4085 or 65525\n')
|
||||
|
||||
def test_boundary_clusters16(self) -> None:
|
||||
output: bytes = check_output(['python', '../fatfsgen.py', '--partition_size', '268390400', 'test_dir'],
|
||||
stderr=STDOUT)
|
||||
self.assertEqual(
|
||||
output,
|
||||
b'WARNING: It is not recommended to create FATFS with bounding count of clusters: 4085 or 65525\n')
|
||||
|
||||
def test_boundary_clusters_fat32(self) -> None:
|
||||
self.assertRaises(NotImplementedError, fatfsgen.FATFS, size=268419193)
|
||||
|
||||
def test_inconsistent_fat12(self) -> None:
|
||||
self.assertRaises(InconsistentFATAttributes, fatfsgen.FATFS, size=20480000, explicit_fat_type=FAT12)
|
||||
|
||||
def test_lfn_increasing(self) -> None:
|
||||
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
|
||||
fatfs.create_directory('VERYLONGTESTFOLD')
|
||||
fatfs.create_file('HELLOHELLOHELLOOOOOOO', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.create_file('HELLOHELLOHELLOOOOOOB', extension='TXT', path_from_root=['VERYLONGTESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLOHELLOHELLOOOOOOO.TXT'],
|
||||
content=b'this is a test A')
|
||||
fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLOHELLOHELLOOOOOOB.TXT'],
|
||||
content=b'this is a test B')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
file_system = read_filesystem(CFG['output_file'])
|
||||
|
||||
self.assertEqual(file_system[0x2000: 0x2010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff')
|
||||
self.assertEqual(file_system[0x2011: 0x2020], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
|
||||
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00')
|
||||
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
|
||||
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~\x01 \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6011: 0x6020], b'\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6040: 0x6050], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\xado\x00')
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
|
||||
|
||||
self.assertEqual(file_system[0x6050: 0x6060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6060: 0x6070], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00')
|
||||
self.assertEqual(file_system[0x6070: 0x6080], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x6080: 0x6090], b'HELLOH~\x01TXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x6090: 0x60a0], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x10\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x60a0: 0x60b0], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\x8do\x00')
|
||||
|
||||
self.assertEqual(file_system[0x60b0: 0x60c0], b'o\x00b\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x60c0: 0x60d0], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\x8dh\x00')
|
||||
self.assertEqual(file_system[0x60d0: 0x60e0], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
|
||||
self.assertEqual(file_system[0x60e0: 0x60f0], b'HELLOH~\x02TXT \x00\x00\x00\x00')
|
||||
|
||||
def test_bs_not_initialized(self) -> None:
|
||||
self.assertEqual(str(BootSector()), 'Boot sector is not initialized!')
|
||||
self.assertRaises(NotInitialized, BootSector().generate_boot_sector)
|
||||
self.assertRaises(NotInitialized, lambda: BootSector().binary_image) # encapsulate property to callable
|
||||
|
||||
def test_bs_str(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
bs = BootSector(fatfs.state.boot_sector_state)
|
||||
bs.generate_boot_sector()
|
||||
bs.parse_boot_sector(bs.binary_image)
|
||||
x = 'FATFS properties:,clusters: 252,data_region_start: 24576,data_sectors: ' \
|
||||
'250,entries_root_count: 512,fat_table_start_address: 4096,fat_tables_cnt: 1,' \
|
||||
'fatfs_type: 12,file_sys_type: FAT ,hidden_sectors: 0,media_type: 248,' \
|
||||
'non_data_sectors: 6,num_heads: 255,oem_name: MSDOS5.0,reserved_sectors_cnt: 1,' \
|
||||
'root_dir_sectors_cnt: 4,root_directory_start: 8192,sec_per_track: 63,sector_size: 4096,' \
|
||||
'sectors_count: 256,sectors_per_cluster: 1,sectors_per_fat_cnt: 1,size: 1048576,' \
|
||||
'volume_label: Espressif ,volume_uuid: 1144419653,'
|
||||
self.assertEqual(x.split(',')[:-2], str(bs).split('\n')[:-2]) # except for volume id
|
||||
|
||||
def test_parsing_error(self) -> None:
|
||||
self.assertRaises(NotInitialized, BootSector().parse_boot_sector, b'')
|
||||
|
||||
def test_not_implemented_fat32(self) -> None:
|
||||
self.assertEqual(
|
||||
Entry.get_cluster_id(
|
||||
Entry.ENTRY_FORMAT_SHORT_NAME.parse(
|
||||
bytearray(b'AHOJ \x18\x00\xb0[&U&U\x00\x00\xb0[&U\x02\x00\x08\x00\x00\x00'))),
|
||||
2)
|
||||
|
||||
def test_get_cluster_value_from_fat(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
self.assertEqual(fatfs.fat.get_cluster_value(1), 0xFFF)
|
||||
|
||||
def test_is_cluster_last(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
self.assertEqual(fatfs.fat.is_cluster_last(2), False)
|
||||
|
||||
def test_chain_in_fat(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
self.assertEqual(fatfs.fat.get_chained_content(1), b'\x00' * 0x1000)
|
||||
|
||||
def test_retrieve_file_chaining(self) -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a')
|
||||
fatfs.write_filesystem(CFG['output_file'])
|
||||
self.assertEqual(fatfs.fat.get_chained_content(1)[:15], b'WRITEF TXT \x00\x00\x00')
|
||||
self.assertEqual(fatfs.fat.get_chained_content(2)[:15], b'aaaaaaaaaaaaaaa')
|
||||
|
||||
def test_lstrip(self) -> None:
|
||||
self.assertEqual(right_strip_string('\x20\x20\x20thisistest\x20\x20\x20'), ' thisistest')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+354
@@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
from subprocess import STDOUT, run
|
||||
|
||||
from test_utils import compare_folders, fill_sector, generate_local_folder_structure, generate_test_dir_2
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import fatfsgen # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.entry import Entry # noqa E402 # pylint: disable=C0413
|
||||
|
||||
|
||||
class FatFSGen(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
os.makedirs('output_data')
|
||||
generate_test_dir_2()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree('output_data', ignore_errors=True)
|
||||
shutil.rmtree('Espressif', ignore_errors=True)
|
||||
shutil.rmtree('testf', ignore_errors=True)
|
||||
shutil.rmtree('testf_wl', ignore_errors=True)
|
||||
|
||||
if os.path.exists('fatfs_image.img'):
|
||||
os.remove('fatfs_image.img')
|
||||
|
||||
@staticmethod
|
||||
def test_gen_parse() -> None:
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
|
||||
'output_data/tst_str'
|
||||
], stderr=STDOUT)
|
||||
|
||||
run(['python', '../fatfsgen.py', 'output_data/tst_str'], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
|
||||
assert set(os.listdir('Espressif')) == {'TEST', 'TESTFILE'}
|
||||
with open('Espressif/TESTFILE', 'rb') as in_:
|
||||
assert in_.read() == b'ahoj\n'
|
||||
|
||||
assert set(os.listdir('Espressif/TEST')) == {'TEST', 'TESTFIL2'}
|
||||
with open('Espressif/TEST/TESTFIL2', 'rb') as in_:
|
||||
assert in_.read() == b'thisistest\n'
|
||||
assert set(os.listdir('Espressif/TEST/TEST')) == {'LASTFILE.TXT'}
|
||||
|
||||
with open('Espressif/TEST/TEST/LASTFILE.TXT', 'rb') as in_:
|
||||
assert in_.read() == b'deeptest\n'
|
||||
|
||||
@staticmethod
|
||||
def test_file_chaining() -> None:
|
||||
fatfs = fatfsgen.FATFS()
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=4096 * b'a' + b'a')
|
||||
fatfs.write_filesystem('fatfs_image.img')
|
||||
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
with open('Espressif/WRITEF.TXT', 'rb') as in_:
|
||||
assert in_.read() == 4097 * b'a'
|
||||
|
||||
@staticmethod
|
||||
def test_full_two_sectors_folder() -> None:
|
||||
fatfs = fatfsgen.FATFS(size=2 * 1024 * 1024)
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
|
||||
for i in range((2 * 4096) // 32):
|
||||
fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD'])
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A253'], content=b'later')
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A255'], content=b'last')
|
||||
fatfs.write_filesystem('fatfs_image.img')
|
||||
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert set(os.listdir('Espressif')) == {'TESTFOLD'}
|
||||
assert set(os.listdir('Espressif/TESTFOLD')) == {f'A{str(i).upper()}' for i in range(256)}
|
||||
|
||||
with open('Espressif/TESTFOLD/A253', 'rb') as in_:
|
||||
assert in_.read() == b'later'
|
||||
|
||||
with open('Espressif/TESTFOLD/A255', 'rb') as in_:
|
||||
assert in_.read() == b'last'
|
||||
|
||||
@staticmethod
|
||||
def test_empty_fat16() -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.write_filesystem('fatfs_image.img')
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
|
||||
@staticmethod
|
||||
def test_chaining_fat16() -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.create_file('WRITEF', extension='TXT')
|
||||
fatfs.write_content(path_from_root=['WRITEF.TXT'], content=4096 * b'a' + b'a')
|
||||
fatfs.write_filesystem('fatfs_image.img')
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
with open('Espressif/WRITEF.TXT', 'rb') as in_:
|
||||
assert in_.read() == 4097 * b'a'
|
||||
|
||||
@staticmethod
|
||||
def test_full_sector_folder_fat16() -> None:
|
||||
fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024)
|
||||
fatfs.create_directory('TESTFOLD')
|
||||
|
||||
fill_sector(fatfs)
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first')
|
||||
fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later')
|
||||
fatfs.write_filesystem('fatfs_image.img')
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert set(os.listdir('Espressif')) == {'TESTFOLD'}
|
||||
assert set(os.listdir('Espressif/TESTFOLD')) == {f'A{str(i).upper()}' for i in range(128)}
|
||||
with open('Espressif/TESTFOLD/A0', 'rb') as in_:
|
||||
assert in_.read() == b'first'
|
||||
|
||||
with open('Espressif/TESTFOLD/A126', 'rb') as in_:
|
||||
assert in_.read() == b'later'
|
||||
|
||||
@staticmethod
|
||||
def file_(x: str, content_: str = 'hey this is a test') -> dict:
|
||||
return {
|
||||
'type': 'file',
|
||||
'name': x,
|
||||
'content': content_
|
||||
}
|
||||
|
||||
def test_e2e_file(self) -> None:
|
||||
struct_: dict = {
|
||||
'type': 'folder',
|
||||
'name': 'testf',
|
||||
'content': [self.file_('NEWF')]
|
||||
}
|
||||
generate_local_folder_structure(struct_, path_='.')
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
shutil.rmtree('Espressif', ignore_errors=True)
|
||||
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
|
||||
def test_e2e_deeper(self) -> None:
|
||||
folder_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ',
|
||||
'content': [
|
||||
self.file_('NEWFLE'),
|
||||
self.file_('NEW.TXT'),
|
||||
self.file_('NEWE.TXT'),
|
||||
self.file_('NEW4.TXT'),
|
||||
self.file_('NEW5.TXT'),
|
||||
]
|
||||
}
|
||||
struct_: dict = {
|
||||
'type': 'folder',
|
||||
'name': 'testf',
|
||||
'content': [
|
||||
self.file_('MY_NEW'),
|
||||
folder_
|
||||
]
|
||||
}
|
||||
|
||||
generate_local_folder_structure(struct_, path_='.')
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
shutil.rmtree('Espressif', ignore_errors=True)
|
||||
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
|
||||
def test_e2e_deeper_large(self) -> None:
|
||||
folder_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ',
|
||||
'content': [
|
||||
self.file_('NEWFLE', content_=4097 * 'a'),
|
||||
self.file_('NEW.TXT', content_=2 * 4097 * 'a'),
|
||||
self.file_('NEWE.TXT'),
|
||||
self.file_('NEW4.TXT'),
|
||||
self.file_('NEW5.TXT'),
|
||||
]
|
||||
}
|
||||
folder2_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ3',
|
||||
'content': [
|
||||
self.file_('NEWFLE', content_=4097 * 'a'),
|
||||
self.file_('NEW.TXT', content_=2 * 4097 * 'a'),
|
||||
self.file_('NEWE.TXT'),
|
||||
self.file_('NEW4.TXT'),
|
||||
self.file_('NEW5.TXT'),
|
||||
]
|
||||
}
|
||||
folder3_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ2',
|
||||
'content': [self.file_(f'A{i}') for i in range(50)]
|
||||
}
|
||||
struct_: dict = {
|
||||
'type': 'folder',
|
||||
'name': 'testf',
|
||||
'content': [
|
||||
self.file_('MY_NEW'),
|
||||
folder_,
|
||||
folder2_,
|
||||
folder3_
|
||||
]
|
||||
}
|
||||
generate_local_folder_structure(struct_, path_='.')
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
shutil.rmtree('Espressif', ignore_errors=True)
|
||||
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
|
||||
def test_e2e_very_deep(self) -> None:
|
||||
folder_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ',
|
||||
'content': [
|
||||
self.file_('NEWFLE', content_=4097 * 'a'),
|
||||
self.file_('NEW.TXT', content_=2 * 4097 * 'a'),
|
||||
self.file_('NEWE.TXT'),
|
||||
self.file_('NEW4.TXT'),
|
||||
self.file_('NEW5.TXT'),
|
||||
]
|
||||
}
|
||||
folder2_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ3',
|
||||
'content': [
|
||||
self.file_('NEWFLE', content_=4097 * 'a'),
|
||||
self.file_('NEW.TXT', content_=2 * 4097 * 'a'),
|
||||
self.file_('NEWE.TXT'),
|
||||
self.file_('NEW4.TXT'),
|
||||
self.file_('NEW5.TXT'),
|
||||
folder_,
|
||||
]
|
||||
}
|
||||
folder3_ = {
|
||||
'type': 'folder',
|
||||
'name': 'XYZ2',
|
||||
'content': [self.file_(f'A{i}') for i in range(50)] + [folder2_]
|
||||
}
|
||||
|
||||
struct_: dict = {
|
||||
'type': 'folder',
|
||||
'name': 'testf',
|
||||
'content': [
|
||||
self.file_('MY_NEW'),
|
||||
folder_,
|
||||
folder2_,
|
||||
folder3_
|
||||
]
|
||||
}
|
||||
generate_local_folder_structure(struct_, path_='.')
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
|
||||
'testf'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
|
||||
def test_e2e_very_deep_long(self) -> None:
|
||||
folder_ = {
|
||||
'type': 'folder',
|
||||
'name': 'veryveryverylong111',
|
||||
'content': [
|
||||
self.file_('myndewveryverylongfile1.txt', content_=4097 * 'a'),
|
||||
self.file_('mynewveryverylongfile22.txt', content_=2 * 4097 * 'a'),
|
||||
self.file_('mynewveryverylongfile333.txt' * 8),
|
||||
self.file_('mynewveryverylongfile4444.txt' * 8),
|
||||
self.file_('mynewveryverylongfile5555.txt'),
|
||||
self.file_('SHORT.TXT'),
|
||||
]
|
||||
}
|
||||
struct_: dict = {
|
||||
'type': 'folder',
|
||||
'name': 'testf',
|
||||
'content': [
|
||||
self.file_('mynewveryverylongfile.txt' * 5),
|
||||
folder_,
|
||||
]
|
||||
}
|
||||
generate_local_folder_structure(struct_, path_='.')
|
||||
run([
|
||||
'python',
|
||||
f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}',
|
||||
'testf', '--long_name_support'
|
||||
], stderr=STDOUT)
|
||||
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
|
||||
assert compare_folders('testf', 'Espressif')
|
||||
|
||||
def test_parse_long_name(self) -> None:
|
||||
self.assertEqual(
|
||||
Entry.parse_entry_long(
|
||||
b'\x01t\x00h\x00i\x00s\x00_\x00\x0f\x00\xfbi\x00s\x00_\x00l\x00o\x00n\x00\x00\x00g\x00_\x00', 251),
|
||||
{
|
||||
'order': 1,
|
||||
'name1': b't\x00h\x00i\x00s\x00_\x00',
|
||||
'name2': b'i\x00s\x00_\x00l\x00o\x00n\x00',
|
||||
'name3': b'g\x00_\x00',
|
||||
'is_last': False
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
Entry.parse_entry_long(
|
||||
b'\x01t\x00h\x00i\x00s\x00_\x00\x0f\x00\xfbi\x00s\x00_\x00l\x00o\x00n\x00\x00\x00g\x00_\x00', 252
|
||||
),
|
||||
{}
|
||||
)
|
||||
self.assertEqual(
|
||||
Entry.parse_entry_long(
|
||||
b'\x01t\x00h\x00i\x00s\x00_\x00\x0f\x01\xfbi\x00s\x00_\x00l\x00o\x00n\x00\x00\x00g\x00_\x00', 251
|
||||
),
|
||||
{}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,67 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import fatfsgen # noqa E402 # pylint: disable=C0413
|
||||
|
||||
CFG = dict(
|
||||
sector_size=4096,
|
||||
entry_size=32,
|
||||
fat_start=0x1000,
|
||||
data_start=0x7000,
|
||||
root_start=0x2000,
|
||||
output_file=os.path.join('output_data', 'tmp_file.img'),
|
||||
test_dir=os.path.join('output_data', 'test'),
|
||||
test_dir2=os.path.join('output_data', 'tst_str'),
|
||||
) # type: Union[Dict[str, Any]]
|
||||
|
||||
|
||||
def generate_test_dir_1() -> None:
|
||||
os.makedirs(os.path.join(CFG['test_dir'], 'test', 'test'))
|
||||
with open(os.path.join(CFG['test_dir'], 'test', 'test', 'lastfile'), 'w') as file:
|
||||
file.write('deeptest\n')
|
||||
with open(os.path.join(CFG['test_dir'], 'test', 'testfil2'), 'w') as file:
|
||||
file.write('thisistest\n')
|
||||
with open(os.path.join(CFG['test_dir'], 'testfile'), 'w') as file:
|
||||
file.write('ahoj\n')
|
||||
|
||||
|
||||
def generate_test_dir_2() -> None:
|
||||
os.makedirs(os.path.join(CFG['test_dir2'], 'test', 'test'))
|
||||
with open(os.path.join(CFG['test_dir2'], 'test', 'test', 'lastfile.txt'), 'w') as file:
|
||||
file.write('deeptest\n')
|
||||
with open(os.path.join(CFG['test_dir2'], 'test', 'testfil2'), 'w') as file:
|
||||
file.write('thisistest\n')
|
||||
with open(os.path.join(CFG['test_dir2'], 'testfile'), 'w') as file:
|
||||
file.write('ahoj\n')
|
||||
|
||||
|
||||
def fill_sector(fatfs: fatfsgen.FATFS, file_prefix: str = 'A') -> None:
|
||||
for i in range(CFG['sector_size'] // CFG['entry_size']):
|
||||
fatfs.create_file(f'{file_prefix}{str(i).upper()}', path_from_root=['TESTFOLD'])
|
||||
|
||||
|
||||
def generate_local_folder_structure(structure_: dict, path_: str) -> None:
|
||||
if structure_['type'] == 'folder':
|
||||
new_path_ = os.path.join(path_, structure_['name'])
|
||||
os.makedirs(new_path_)
|
||||
for item_ in structure_['content']:
|
||||
generate_local_folder_structure(item_, new_path_)
|
||||
else:
|
||||
new_path_ = os.path.join(path_, structure_['name'])
|
||||
with open(new_path_, 'w') as f_:
|
||||
f_.write(structure_['content'])
|
||||
|
||||
|
||||
def compare_folders(fp1: str, fp2: str) -> bool:
|
||||
if os.path.isdir(fp1) != os.path.isdir(fp2):
|
||||
return False
|
||||
if os.path.isdir(fp1):
|
||||
if set(os.listdir(fp1)) != set(os.listdir(fp2)):
|
||||
return False
|
||||
return all([compare_folders(os.path.join(fp1, path_), os.path.join(fp2, path_)) for path_ in os.listdir(fp1)])
|
||||
with open(fp1, 'rb') as f1_, open(fp2, 'rb') as f2_:
|
||||
return f1_.read() == f2_.read()
|
||||
Executable
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from test_utils import CFG, generate_test_dir_1, generate_test_dir_2
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import wl_fatfsgen # noqa E402 # pylint: disable=C0413
|
||||
from fatfs_utils.exceptions import WLNotInitialized # noqa E402 # pylint: disable=C0413
|
||||
|
||||
|
||||
class WLFatFSGen(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
os.makedirs('output_data')
|
||||
generate_test_dir_1()
|
||||
generate_test_dir_2()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree('output_data')
|
||||
|
||||
def test_empty_file_sn_fat12(self) -> None:
|
||||
fatfs = wl_fatfsgen.WLFATFS()
|
||||
fatfs.plain_fatfs.create_file('TESTFILE')
|
||||
fatfs.init_wl()
|
||||
fatfs.wl_write_filesystem(CFG['output_file'])
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = fs_file.read()
|
||||
|
||||
self.assertEqual(file_system[0x3000:0x300c], b'TESTFILE \x20') # check entry name and type
|
||||
self.assertEqual(file_system[0x2000:0x2006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
|
||||
|
||||
def test_directory_sn_fat12(self) -> None:
|
||||
fatfs = wl_fatfsgen.WLFATFS(device_id=3750448905)
|
||||
fatfs.plain_fatfs.create_directory('TESTFOLD')
|
||||
fatfs.init_wl()
|
||||
|
||||
fatfs.wl_write_filesystem(CFG['output_file'])
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = fs_file.read()
|
||||
|
||||
# boot sector
|
||||
self.assertEqual(file_system[0x1000:0x1010], b'\xeb\xfe\x90MSDOS5.0\x00\x10\x01\x01\x00')
|
||||
self.assertEqual(file_system[0x1010:0x1020], b'\x01\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x102b:0x1034], b'Espressif')
|
||||
|
||||
self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10') # check entry name and type
|
||||
self.assertEqual(file_system[0x2000:0x2006], b'\xf8\xff\xff\xff\x0f\x00') # check fat
|
||||
self.assertEqual(file_system[0x7000:0x700c], b'. \x10') # reference to itself
|
||||
self.assertEqual(file_system[0x7020:0x702c], b'.. \x10') # reference to parent
|
||||
|
||||
# check state1
|
||||
self.assertEqual(file_system[0xfb000:0xfb00f], b'\x00\x00\x00\x00\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xfb010:0xfb020], b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
|
||||
self.assertEqual(file_system[0xfb020:0xfb02f], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xfb031:0xfb040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\xa1\x94i')
|
||||
|
||||
# check state2
|
||||
self.assertEqual(file_system[0xfd000:0xfd00f], b'\x00\x00\x00\x00\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xfd010:0xfd020], b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
|
||||
self.assertEqual(file_system[0xfd020:0xfd02f], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xfd031:0xfd040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\xa1\x94i')
|
||||
|
||||
# check config
|
||||
self.assertEqual(file_system[0xff001:0xff010], b'\x00\x00\x00\x00\x00\x10\x00\x00\x10\x00\x00\x00\x10\x00\x00')
|
||||
self.assertEqual(file_system[0xff010:0xff01f], b'\x10\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00 \x00\x00')
|
||||
self.assertEqual(file_system[0xff020:0xff030], b'\xe0b\xb5O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xff030:0xff03f], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
|
||||
|
||||
def test_directory_sn_fat122mb(self) -> None:
|
||||
fatfs = wl_fatfsgen.WLFATFS(device_id=3750448905, size=2 * 1024 * 1024)
|
||||
fatfs.plain_fatfs.create_directory('TESTFOLD')
|
||||
fatfs.init_wl()
|
||||
|
||||
fatfs.wl_write_filesystem(CFG['output_file'])
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = fs_file.read()
|
||||
|
||||
# check state1
|
||||
self.assertEqual(file_system[0x1f9000:0x1f900e], b'\x00\x00\x00\x00\xf9\x01\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x1f9010:0x1f9020],
|
||||
b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
|
||||
self.assertEqual(file_system[0x1f9020:0x1f902e], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x1f9030:0x1f9040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j5\xbdp')
|
||||
|
||||
# check state2
|
||||
self.assertEqual(file_system[0x1fc000:0x1fc00e], b'\x00\x00\x00\x00\xf9\x01\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x1fc010:0x1fc020],
|
||||
b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf')
|
||||
self.assertEqual(file_system[0x1fc020:0x1fc02e], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x1fc030:0x1fc040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j5\xbdp')
|
||||
|
||||
# check config
|
||||
self.assertEqual(file_system[0x1ff000:0x1ff00f], b'\x00\x00\x00\x00\x00\x00 \x00\x00\x10\x00\x00\x00\x10\x00')
|
||||
self.assertEqual(file_system[0x1ff010:0x1ff01f], b'\x10\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00 \x00\x00')
|
||||
self.assertEqual(file_system[0x1ff020:0x1ff030], b')\x892j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x1ff030:0x1ff03e], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
|
||||
|
||||
def test_write_not_initialized_wlfatfs(self) -> None:
|
||||
fatfs = wl_fatfsgen.WLFATFS()
|
||||
fatfs.plain_fatfs.create_directory('TESTFOLD')
|
||||
self.assertRaises(WLNotInitialized, fatfs.wl_write_filesystem, CFG['output_file'])
|
||||
|
||||
def test_e2e_deep_folder_into_image_ext(self) -> None:
|
||||
fatfs = wl_fatfsgen.WLFATFS()
|
||||
fatfs.plain_fatfs.generate(CFG['test_dir2'])
|
||||
fatfs.init_wl()
|
||||
fatfs.wl_write_filesystem(CFG['output_file'])
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = bytearray(fs_file.read())
|
||||
|
||||
self.assertEqual(file_system[0x3020:0x3030], b'TESTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8000:0x8010], b'. \x10\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILETXT \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xc000:0xc009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff')
|
||||
|
||||
def test_e2e_deep_folder_into_image(self) -> None:
|
||||
fatfs = wl_fatfsgen.WLFATFS()
|
||||
fatfs.plain_fatfs.generate(CFG['test_dir'])
|
||||
fatfs.init_wl()
|
||||
fatfs.wl_write_filesystem(CFG['output_file'])
|
||||
with open(CFG['output_file'], 'rb') as fs_file:
|
||||
file_system = bytearray(fs_file.read())
|
||||
|
||||
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x7070:0x7080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILE \x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00')
|
||||
self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/sdmmc_types.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "ff.h"
|
||||
#include "wear_levelling.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @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_ctx.
|
||||
* Difference between this function and the one above is that this one
|
||||
* will release the correct drive, while the one above will release
|
||||
* the last registered one
|
||||
*
|
||||
* @param base_path path prefix where FATFS is registered. This is the same
|
||||
* used when esp_vfs_fat_register was called
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_unregister_path(const char* base_path);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Configuration arguments for esp_vfs_fat_sdmmc_mount and esp_vfs_fat_spiflash_mount_rw_wl functions
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* If FAT partition can not be mounted, and this parameter is true,
|
||||
* create partition table and format the filesystem.
|
||||
*/
|
||||
bool format_if_mount_failed;
|
||||
int max_files; ///< Max number of open files
|
||||
/**
|
||||
* If format_if_mount_failed is set, and mount fails, format the card
|
||||
* with given allocation unit size. Must be a power of 2, between sector
|
||||
* size and 128 * sector size.
|
||||
* For SD cards, sector size is always 512 bytes. For wear_levelling,
|
||||
* sector size is determined by CONFIG_WL_SECTOR_SIZE option.
|
||||
*
|
||||
* Using larger allocation unit size will result in higher read/write
|
||||
* performance and higher overhead when storing small files.
|
||||
*
|
||||
* Setting this field to 0 will result in allocation unit set to the
|
||||
* sector size.
|
||||
*/
|
||||
size_t allocation_unit_size;
|
||||
/**
|
||||
* Enables real ff_disk_status function implementation for SD cards
|
||||
* (ff_sdmmc_status). Possibly slows down IO performance.
|
||||
*
|
||||
* Try to enable if you need to handle situations when SD cards
|
||||
* are not unmounted properly before physical removal
|
||||
* or you are experiencing issues with SD cards.
|
||||
*
|
||||
* Doesn't do anything for other memory storage media.
|
||||
*/
|
||||
bool disk_status_check_enable;
|
||||
} esp_vfs_fat_mount_config_t;
|
||||
|
||||
// Compatibility definition
|
||||
typedef esp_vfs_fat_mount_config_t 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 SDMMC driver or SPI driver with configuration in host_config
|
||||
* - initializes SD card with configuration in slot_config
|
||||
* - mounts FAT partition on SD 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.
|
||||
*
|
||||
* @note Use this API to mount a card through SDSPI is deprecated. Please call
|
||||
* `esp_vfs_fat_sdspi_mount()` instead for that case.
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/sdcard")
|
||||
* @param host_config Pointer to structure describing SDMMC host. When using
|
||||
* SDMMC peripheral, this structure can be initialized using
|
||||
* SDMMC_HOST_DEFAULT() macro. When using SPI peripheral,
|
||||
* this structure can be initialized using SDSPI_HOST_DEFAULT()
|
||||
* macro.
|
||||
* @param slot_config Pointer to structure with slot configuration.
|
||||
* For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t
|
||||
* structure initialized using SDMMC_SLOT_CONFIG_DEFAULT.
|
||||
* @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 or SPI drivers, SDMMC protocol, or FATFS drivers
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config,
|
||||
const void* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card);
|
||||
|
||||
/**
|
||||
* @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 an SPI Master device based on the SPI Master driver with configuration in
|
||||
* slot_config, and attach it to an initialized SPI bus.
|
||||
* - initializes SD card with configuration in host_config_input
|
||||
* - mounts FAT partition on SD 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.
|
||||
*
|
||||
* @note This function try to attach the new SD SPI device to the bus specified in host_config.
|
||||
* Make sure the SPI bus specified in `host_config->slot` have been initialized by
|
||||
* `spi_bus_initialize()` before.
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/sdcard")
|
||||
* @param host_config_input Pointer to structure describing SDMMC host. This structure can be
|
||||
* initialized using SDSPI_HOST_DEFAULT() macro.
|
||||
* @param slot_config Pointer to structure with slot configuration.
|
||||
* For SPI peripheral, pass a pointer to sdspi_device_config_t
|
||||
* structure initialized using SDSPI_DEVICE_CONFIG_DEFAULT().
|
||||
* @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. It is suggested to hold this handle and use it to unmount the card later if
|
||||
* needed. Otherwise it's not suggested to use more than one card at the same time and unmount one
|
||||
* of them in your application.
|
||||
* @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 or SPI drivers, SDMMC protocol, or FATFS drivers
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config_input,
|
||||
const sdspi_device_config_t* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card);
|
||||
|
||||
/**
|
||||
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_sdmmc_mount
|
||||
*
|
||||
* @deprecated Use `esp_vfs_fat_sdcard_unmount()` instead.
|
||||
*
|
||||
* @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(void) __attribute__((deprecated("Please use esp_vfs_fat_sdcard_unmount instead")));
|
||||
|
||||
/**
|
||||
* @brief Unmount an SD card from the FAT filesystem and release resources acquired using
|
||||
* `esp_vfs_fat_sdmmc_mount()` or `esp_vfs_fat_sdspi_mount()`
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if the card argument is unregistered
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdcard_unmount(const char* base_path, sdmmc_card_t *card);
|
||||
|
||||
/**
|
||||
* @brief Format FAT filesystem
|
||||
*
|
||||
* @note
|
||||
* This API should be only called when the FAT is already mounted.
|
||||
*
|
||||
* @param base_path Path where partition should be registered (e.g. "/sdcard")
|
||||
* @param card Pointer to the card handle, which should be initialised by calling `esp_vfs_fat_sdspi_mount` first
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK
|
||||
* - ESP_ERR_INVALID_STATE: FAT partition isn't mounted, call esp_vfs_fat_sdmmc_mount or esp_vfs_fat_sdspi_mount first
|
||||
* - ESP_ERR_NO_MEM: if memory can not be allocated
|
||||
* - ESP_FAIL: fail to format it, or fail to mount back
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card);
|
||||
|
||||
/**
|
||||
* @brief Convenience function to initialize FAT filesystem in SPI flash and register it in VFS
|
||||
*
|
||||
* This is an all-in-one function which does the following:
|
||||
*
|
||||
* - finds the partition with defined partition_label. Partition label should be
|
||||
* configured in the partition table.
|
||||
* - initializes flash wear levelling library on top of the given partition
|
||||
* - mounts FAT partition using FATFS library on top of flash wear levelling
|
||||
* library
|
||||
* - registers FATFS library with VFS, with prefix given by base_prefix variable
|
||||
*
|
||||
* This function is intended to make example code more compact.
|
||||
*
|
||||
* @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
|
||||
* @param partition_label label of the partition which should be used
|
||||
* @param mount_config pointer to structure with extra parameters for mounting FATFS
|
||||
* @param[out] wl_handle wear levelling driver handle
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_rw_wl 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 wear levelling library, SPI flash driver, or FATFS drivers
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
wl_handle_t* wl_handle);
|
||||
|
||||
/**
|
||||
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_spiflash_mount_rw_wl
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/spiflash")
|
||||
* @param wl_handle wear levelling driver handle returned by esp_vfs_fat_spiflash_mount_rw_wl
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_rw_wl hasn't been called
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t wl_handle);
|
||||
|
||||
/**
|
||||
* @brief Format FAT filesystem
|
||||
*
|
||||
* @note
|
||||
* This API can be called when the FAT is mounted / not mounted.
|
||||
* If this API is called when the FAT isn't mounted (by calling esp_vfs_fat_spiflash_mount_rw_wl),
|
||||
* this API will first mount the FAT then format it, then restore back to the original state.
|
||||
*
|
||||
* @param base_path Path where partition should be registered (e.g. "/spiflash")
|
||||
* @param partition_label Label of the partition which should be used
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK
|
||||
* - ESP_ERR_NO_MEM: if memory can not be allocated
|
||||
* - Other errors from esp_vfs_fat_spiflash_mount_rw_wl
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label);
|
||||
|
||||
/**
|
||||
* @brief Convenience function to initialize read-only FAT filesystem and register it in VFS
|
||||
*
|
||||
* This is an all-in-one function which does the following:
|
||||
*
|
||||
* - finds the partition with defined partition_label. Partition label should be
|
||||
* configured in the partition table.
|
||||
* - mounts FAT partition using FATFS library
|
||||
* - registers FATFS library with VFS, with prefix given by base_prefix variable
|
||||
*
|
||||
* @note Wear levelling is not used when FAT is mounted in read-only mode using this function.
|
||||
*
|
||||
* @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
|
||||
* @param partition_label label of the partition which should be used
|
||||
* @param mount_config pointer to structure with extra parameters for mounting FATFS
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_ro was already called for the same partition
|
||||
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||
* - ESP_FAIL if partition can not be mounted
|
||||
* - other error codes from SPI flash driver, or FATFS drivers
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config);
|
||||
|
||||
/**
|
||||
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_spiflash_mount_ro
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/spiflash")
|
||||
* @param partition_label label of partition to be unmounted
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_ro hasn't been called
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* partition_label);
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
wl_handle_t* wl_handle)
|
||||
__attribute__((deprecated("esp_vfs_fat_spiflash_mount is deprecated, please use esp_vfs_fat_spiflash_mount_rw_wl instead")));
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount(const char* base_path, wl_handle_t wl_handle)
|
||||
__attribute__((deprecated("esp_vfs_fat_spiflash_unmount is deprecated, please use esp_vfs_fat_spiflash_unmount_rw_wl instead")));
|
||||
esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config)
|
||||
__attribute__((deprecated("esp_vfs_fat_rawflash_mount is deprecated, please use esp_vfs_fat_spiflash_mount_ro instead")));
|
||||
esp_err_t esp_vfs_fat_rawflash_unmount(const char* base_path, const char* partition_label)
|
||||
__attribute__((deprecated("esp_vfs_fat_rawflash_unmount is deprecated, please use esp_vfs_fat_spiflash_unmount_ro instead")));
|
||||
|
||||
/**
|
||||
* @brief Get information for FATFS partition
|
||||
*
|
||||
* @param base_path Path where partition should be registered (e.g. "/spiflash")
|
||||
* @param[out] out_total_bytes Size of the file system
|
||||
* @param[out] out_free_bytes Current used bytes in the file system
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if partition not found
|
||||
* - ESP_FAIL if another FRESULT error (saved in errno)
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_info(const char* base_path, uint64_t* out_total_bytes, uint64_t* out_free_bytes);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 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 "esp_vfs_fat.h"
|
||||
#include <sys/param.h>
|
||||
#include <stddef.h>
|
||||
|
||||
static inline size_t esp_vfs_fat_get_allocation_unit_size(
|
||||
size_t sector_size, size_t requested_size)
|
||||
{
|
||||
size_t alloc_unit_size = requested_size;
|
||||
const size_t max_sectors_per_cylinder = 128;
|
||||
const size_t max_size = sector_size * max_sectors_per_cylinder;
|
||||
alloc_unit_size = MAX(alloc_unit_size, sector_size);
|
||||
alloc_unit_size = MIN(alloc_unit_size, max_size);
|
||||
return alloc_unit_size;
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "vfs_fat_internal.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_sdmmc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
|
||||
#if SOC_SDMMC_HOST_SUPPORTED
|
||||
#include "driver/sdmmc_host.h"
|
||||
#endif
|
||||
|
||||
static const char* TAG = "vfs_fat_sdmmc";
|
||||
|
||||
#define CHECK_EXECUTE_RESULT(err, str) do { \
|
||||
if ((err) !=ESP_OK) { \
|
||||
ESP_LOGE(TAG, str" (0x%x).", err); \
|
||||
goto cleanup; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
typedef struct vfs_fat_sd_ctx_t {
|
||||
BYTE pdrv; //Drive number that is mounted
|
||||
esp_vfs_fat_mount_config_t mount_config; //Mount configuration
|
||||
FATFS *fs; //FAT structure pointer that is registered
|
||||
sdmmc_card_t *card; //Card info
|
||||
char *base_path; //Path where partition is registered
|
||||
} vfs_fat_sd_ctx_t;
|
||||
|
||||
static vfs_fat_sd_ctx_t *s_ctx[FF_VOLUMES] = {};
|
||||
/**
|
||||
* This `s_saved_ctx_id` is only used by `esp_vfs_fat_sdmmc_unmount`, which is deprecated.
|
||||
* This variable together with `esp_vfs_fat_sdmmc_unmount` should be removed in next major version
|
||||
*/
|
||||
static uint32_t s_saved_ctx_id = FF_VOLUMES;
|
||||
|
||||
static void call_host_deinit(const sdmmc_host_t *host_config);
|
||||
static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
|
||||
const char *drv, sdmmc_card_t *card, BYTE pdrv);
|
||||
|
||||
static bool s_get_context_id_by_card(const sdmmc_card_t *card, uint32_t *out_id)
|
||||
{
|
||||
vfs_fat_sd_ctx_t *p_ctx = NULL;
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
p_ctx = s_ctx[i];
|
||||
if (p_ctx) {
|
||||
if (p_ctx->card == card) {
|
||||
*out_id = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t s_get_unused_context_id(void)
|
||||
{
|
||||
for (uint32_t i = 0; i < FF_VOLUMES; i++) {
|
||||
if (!s_ctx[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return FF_VOLUMES;
|
||||
}
|
||||
|
||||
static esp_err_t mount_prepare_mem(const char *base_path,
|
||||
BYTE *out_pdrv,
|
||||
char **out_dup_path,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
char* dup_path = NULL;
|
||||
sdmmc_card_t* card = NULL;
|
||||
|
||||
// connect SDMMC driver to FATFS
|
||||
BYTE pdrv = FF_DRV_NOT_USED;
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == FF_DRV_NOT_USED) {
|
||||
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// not using ff_memalloc here, as allocation in internal RAM is preferred
|
||||
card = (sdmmc_card_t*)malloc(sizeof(sdmmc_card_t));
|
||||
if (card == NULL) {
|
||||
ESP_LOGD(TAG, "could not locate new sdmmc_card_t");
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
dup_path = strdup(base_path);
|
||||
if(!dup_path){
|
||||
ESP_LOGD(TAG, "could not copy base_path");
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
*out_card = card;
|
||||
*out_pdrv = pdrv;
|
||||
*out_dup_path = dup_path;
|
||||
return ESP_OK;
|
||||
cleanup:
|
||||
free(card);
|
||||
free(dup_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t s_f_mount(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8_t pdrv, const esp_vfs_fat_mount_config_t *mount_config)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
FRESULT res = f_mount(fs, drv, 1);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGW(TAG, "failed to mount card (%d)", res);
|
||||
|
||||
bool need_mount_again = (res == FR_NO_FILESYSTEM || res == FR_INT_ERR) && mount_config->format_if_mount_failed;
|
||||
if (!need_mount_again) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
err = partition_card(mount_config, drv, card, pdrv);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "mounting again");
|
||||
res = f_mount(fs, drv, 0);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t mount_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card, uint8_t pdrv,
|
||||
const char *base_path, FATFS **out_fs)
|
||||
{
|
||||
FATFS *fs = NULL;
|
||||
esp_err_t err;
|
||||
ff_diskio_register_sdmmc(pdrv, card);
|
||||
ff_sdmmc_set_disk_status_check(pdrv, mount_config->disk_status_check_enable);
|
||||
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
|
||||
// connect FATFS to VFS
|
||||
err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
|
||||
*out_fs = 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
|
||||
err = s_f_mount(card, fs, drv, pdrv, mount_config);
|
||||
if (err != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
if (fs) {
|
||||
f_mount(NULL, drv, 0);
|
||||
}
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
ff_diskio_unregister(pdrv);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
|
||||
const char *drv, sdmmc_card_t *card, BYTE pdrv)
|
||||
{
|
||||
FRESULT res = FR_OK;
|
||||
esp_err_t err;
|
||||
const size_t workbuf_size = 4096;
|
||||
void* workbuf = NULL;
|
||||
ESP_LOGW(TAG, "partitioning card");
|
||||
|
||||
workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
LBA_t plist[] = {100, 0, 0, 0};
|
||||
res = f_fdisk(pdrv, plist, workbuf);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_fdisk failed (%d)", res);
|
||||
goto fail;
|
||||
}
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
|
||||
card->csd.sector_size,
|
||||
mount_config->allocation_unit_size);
|
||||
ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
|
||||
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
|
||||
res = f_mkfs(drv, &opt, workbuf, workbuf_size);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
free(workbuf);
|
||||
return ESP_OK;
|
||||
fail:
|
||||
free(workbuf);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if SOC_SDMMC_HOST_SUPPORTED
|
||||
static esp_err_t init_sdmmc_host(int slot, const void *slot_config, int *out_slot)
|
||||
{
|
||||
*out_slot = slot;
|
||||
return sdmmc_host_init_slot(slot, (const sdmmc_slot_config_t*) slot_config);
|
||||
}
|
||||
|
||||
|
||||
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config,
|
||||
const void* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
esp_err_t err;
|
||||
vfs_fat_sd_ctx_t *ctx = NULL;
|
||||
uint32_t ctx_id = FF_VOLUMES;
|
||||
FATFS *fs = NULL;
|
||||
int card_handle = -1; //uninitialized
|
||||
sdmmc_card_t* card = NULL;
|
||||
BYTE pdrv = FF_DRV_NOT_USED;
|
||||
char* dup_path = NULL;
|
||||
bool host_inited = false;
|
||||
|
||||
err = mount_prepare_mem(base_path, &pdrv, &dup_path, &card);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mount_prepare failed");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = (*host_config->init)();
|
||||
CHECK_EXECUTE_RESULT(err, "host init failed");
|
||||
//deinit() needs to be called to revert the init
|
||||
host_inited = true;
|
||||
//If this failed (indicated by card_handle != -1), slot deinit needs to called()
|
||||
//leave card_handle as is to indicate that (though slot deinit not implemented yet.
|
||||
err = init_sdmmc_host(host_config->slot, slot_config, &card_handle);
|
||||
CHECK_EXECUTE_RESULT(err, "slot init failed");
|
||||
|
||||
// probe and initialize card
|
||||
err = sdmmc_card_init(host_config, card);
|
||||
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
|
||||
|
||||
err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs);
|
||||
CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
|
||||
|
||||
if (out_card != NULL) {
|
||||
*out_card = card;
|
||||
}
|
||||
//For deprecation backward compatibility
|
||||
if (s_saved_ctx_id == FF_VOLUMES) {
|
||||
s_saved_ctx_id = 0;
|
||||
}
|
||||
|
||||
ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
|
||||
if (!ctx) {
|
||||
CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
|
||||
}
|
||||
ctx->pdrv = pdrv;
|
||||
memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
|
||||
ctx->card = card;
|
||||
ctx->base_path = dup_path;
|
||||
ctx->fs = fs;
|
||||
ctx_id = s_get_unused_context_id();
|
||||
assert(ctx_id != FF_VOLUMES);
|
||||
s_ctx[ctx_id] = ctx;
|
||||
|
||||
return ESP_OK;
|
||||
cleanup:
|
||||
if (host_inited) {
|
||||
call_host_deinit(host_config);
|
||||
}
|
||||
free(card);
|
||||
free(dup_path);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static esp_err_t init_sdspi_host(int slot, const void *slot_config, int *out_slot)
|
||||
{
|
||||
esp_err_t err = sdspi_host_init_device((const sdspi_device_config_t*)slot_config, out_slot);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG,
|
||||
"Failed to attach sdspi device onto an SPI bus (rc=0x%x), please initialize the \
|
||||
bus first and check the device parameters."
|
||||
, err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config_input,
|
||||
const sdspi_device_config_t* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
const sdmmc_host_t* host_config = host_config_input;
|
||||
esp_err_t err;
|
||||
vfs_fat_sd_ctx_t *ctx = NULL;
|
||||
uint32_t ctx_id = FF_VOLUMES;
|
||||
FATFS *fs = NULL;
|
||||
int card_handle = -1; //uninitialized
|
||||
bool host_inited = false;
|
||||
BYTE pdrv = FF_DRV_NOT_USED;
|
||||
sdmmc_card_t* card = NULL;
|
||||
char* dup_path = NULL;
|
||||
|
||||
err = mount_prepare_mem(base_path, &pdrv, &dup_path, &card);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mount_prepare failed");
|
||||
return err;
|
||||
}
|
||||
|
||||
//the init() function is usually empty, doesn't require any deinit to revert it
|
||||
err = (*host_config->init)();
|
||||
CHECK_EXECUTE_RESULT(err, "host init failed");
|
||||
|
||||
err = init_sdspi_host(host_config->slot, slot_config, &card_handle);
|
||||
CHECK_EXECUTE_RESULT(err, "slot init failed");
|
||||
//Set `host_inited` to true to indicate that host_config->deinit() needs
|
||||
//to be called to revert `init_sdspi_host`
|
||||
host_inited = true;
|
||||
|
||||
/*
|
||||
* The `slot` argument inside host_config should be replaced by the SD SPI handled returned
|
||||
* above. But the input pointer is const, so create a new variable.
|
||||
*/
|
||||
sdmmc_host_t new_config;
|
||||
if (card_handle != host_config->slot) {
|
||||
new_config = *host_config_input;
|
||||
host_config = &new_config;
|
||||
new_config.slot = card_handle;
|
||||
}
|
||||
|
||||
// probe and initialize card
|
||||
err = sdmmc_card_init(host_config, card);
|
||||
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
|
||||
|
||||
err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs);
|
||||
CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
|
||||
|
||||
if (out_card != NULL) {
|
||||
*out_card = card;
|
||||
}
|
||||
//For deprecation backward compatibility
|
||||
if (s_saved_ctx_id == FF_VOLUMES) {
|
||||
s_saved_ctx_id = 0;
|
||||
}
|
||||
|
||||
ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
|
||||
if (!ctx) {
|
||||
CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
|
||||
}
|
||||
ctx->pdrv = pdrv;
|
||||
memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
|
||||
ctx->card = card;
|
||||
ctx->base_path = dup_path;
|
||||
ctx->fs = fs;
|
||||
ctx_id = s_get_unused_context_id();
|
||||
assert(ctx_id != FF_VOLUMES);
|
||||
s_ctx[ctx_id] = ctx;
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
cleanup:
|
||||
if (host_inited) {
|
||||
call_host_deinit(host_config);
|
||||
}
|
||||
free(card);
|
||||
free(dup_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void call_host_deinit(const sdmmc_host_t *host_config)
|
||||
{
|
||||
if (host_config->flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
|
||||
host_config->deinit_p(host_config->slot);
|
||||
} else {
|
||||
host_config->deinit();
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t unmount_card_core(const char *base_path, sdmmc_card_t *card)
|
||||
{
|
||||
BYTE pdrv = ff_diskio_get_pdrv_card(card);
|
||||
if (pdrv == 0xff) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// unmount
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
// release SD driver
|
||||
ff_diskio_unregister(pdrv);
|
||||
|
||||
call_host_deinit(&card->host);
|
||||
free(card);
|
||||
|
||||
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdmmc_unmount(void)
|
||||
{
|
||||
esp_err_t err = unmount_card_core(s_ctx[s_saved_ctx_id]->base_path, s_ctx[s_saved_ctx_id]->card);
|
||||
free(s_ctx[s_saved_ctx_id]);
|
||||
s_ctx[s_saved_ctx_id] = NULL;
|
||||
s_saved_ctx_id = FF_VOLUMES;
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card)
|
||||
{
|
||||
uint32_t id = FF_VOLUMES;
|
||||
bool found = s_get_context_id_by_card(card, &id);
|
||||
if (!found) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
free(s_ctx[id]);
|
||||
s_ctx[id] = NULL;
|
||||
|
||||
esp_err_t err = unmount_card_core(base_path, card);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
if (!card) {
|
||||
ESP_LOGE(TAG, "card not initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
BYTE pdrv = ff_diskio_get_pdrv_card(card);
|
||||
if (pdrv == 0xff) {
|
||||
ESP_LOGE(TAG, "card driver not registered");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
const size_t workbuf_size = 4096;
|
||||
void *workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
//unmount
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
|
||||
//format
|
||||
uint32_t id = FF_VOLUMES;
|
||||
bool found = s_get_context_id_by_card(card, &id);
|
||||
assert(found);
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
|
||||
card->csd.sector_size,
|
||||
s_ctx[id]->mount_config.allocation_unit_size);
|
||||
ESP_LOGI(TAG, "Formatting card, allocation unit size=%d", alloc_unit_size);
|
||||
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
|
||||
FRESULT res = f_mkfs(drv, &opt, workbuf, workbuf_size);
|
||||
free(workbuf);
|
||||
if (res != FR_OK) {
|
||||
ret = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
|
||||
}
|
||||
|
||||
//mount back
|
||||
esp_err_t err = s_f_mount(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config);
|
||||
if (err != ESP_OK) {
|
||||
unmount_card_core(base_path, card);
|
||||
ESP_LOGE(TAG, "failed to format, resources recycled, please mount again");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "vfs_fat_internal.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_rawflash.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "diskio_wl.h"
|
||||
|
||||
static const char* TAG = "vfs_fat_spiflash";
|
||||
|
||||
typedef struct vfs_fat_spiflash_ctx_t {
|
||||
const esp_partition_t *partition; //The partition where the FAT is located
|
||||
bool by_label; //If the partition is mounted by lable or not
|
||||
BYTE pdrv; //Drive number that is mounted
|
||||
FATFS *fs; //FAT structure pointer that is registered
|
||||
wl_handle_t wlhandle; //WL handle
|
||||
esp_vfs_fat_mount_config_t mount_config; //Mount configuration
|
||||
} vfs_fat_spiflash_ctx_t;
|
||||
|
||||
static vfs_fat_spiflash_ctx_t *s_ctx[FF_VOLUMES] = {};
|
||||
|
||||
static bool s_get_context_id_by_label(const char *label, uint32_t *out_id)
|
||||
{
|
||||
vfs_fat_spiflash_ctx_t *p_ctx = NULL;
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
p_ctx = s_ctx[i];
|
||||
if (p_ctx) {
|
||||
if (!label && !p_ctx->by_label) {
|
||||
*out_id = i;
|
||||
return true;
|
||||
}
|
||||
if (label && p_ctx->by_label && strncmp(label, p_ctx->partition->label, 20) == 0) {
|
||||
*out_id = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool s_get_context_id_by_wl_handle(wl_handle_t wlhandle, uint32_t *out_id)
|
||||
{
|
||||
vfs_fat_spiflash_ctx_t *p_ctx = NULL;
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
p_ctx = s_ctx[i];
|
||||
if (p_ctx) {
|
||||
if (p_ctx->wlhandle == wlhandle) {
|
||||
*out_id = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t s_get_unused_context_id(void)
|
||||
{
|
||||
for (uint32_t i = 0; i < FF_VOLUMES; i++) {
|
||||
if (!s_ctx[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return FF_VOLUMES;
|
||||
}
|
||||
|
||||
static esp_err_t s_f_mount_rw(FATFS *fs, const char *drv, const esp_vfs_fat_mount_config_t *mount_config)
|
||||
{
|
||||
FRESULT fresult = f_mount(fs, drv, 1);
|
||||
if (fresult != FR_OK) {
|
||||
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
|
||||
|
||||
bool need_mount_again = (fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR) && mount_config->format_if_mount_failed;
|
||||
if (!need_mount_again) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
const size_t workbuf_size = 4096;
|
||||
void *workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, mount_config->allocation_unit_size);
|
||||
ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
|
||||
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size};
|
||||
fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
|
||||
free(workbuf);
|
||||
workbuf = NULL;
|
||||
ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mkfs failed (%d)", fresult);
|
||||
|
||||
ESP_LOGI(TAG, "Mounting again");
|
||||
fresult = f_mount(fs, drv, 0);
|
||||
ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mount failed after formatting (%d)", fresult);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
wl_handle_t* wl_handle)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
vfs_fat_spiflash_ctx_t *ctx = NULL;
|
||||
uint32_t ctx_id = FF_VOLUMES;
|
||||
|
||||
esp_partition_subtype_t subtype = partition_label ?
|
||||
ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_FAT;
|
||||
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
|
||||
subtype, partition_label);
|
||||
ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
|
||||
|
||||
ESP_RETURN_ON_ERROR(wl_mount(data_partition, wl_handle), TAG, "failed to mount wear levelling layer. ret = %i", ret);
|
||||
|
||||
// connect driver to FATFS
|
||||
BYTE pdrv = 0xFF;
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
ESP_GOTO_ON_ERROR(ff_diskio_register_wl_partition(pdrv, *wl_handle), fail, TAG, "ff_diskio_register_wl_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret);
|
||||
|
||||
FATFS *fs;
|
||||
ret = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
// it's okay, already registered with VFS
|
||||
} else if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Try to mount partition
|
||||
ret = s_f_mount_rw(fs, drv, mount_config);
|
||||
if (ret != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ctx = calloc(sizeof(vfs_fat_spiflash_ctx_t), 1);
|
||||
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, fail, TAG, "no mem");
|
||||
ctx->partition = data_partition;
|
||||
ctx->by_label = (partition_label != NULL);
|
||||
ctx->pdrv = pdrv;
|
||||
ctx->fs = fs;
|
||||
ctx->wlhandle = *wl_handle;
|
||||
memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
|
||||
ctx_id = s_get_unused_context_id();
|
||||
//At this stage, we should always get a free context, otherwise program should return already
|
||||
assert(ctx_id != FF_VOLUMES);
|
||||
s_ctx[ctx_id] = ctx;
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
ff_diskio_unregister(pdrv);
|
||||
free(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t wl_handle)
|
||||
{
|
||||
BYTE pdrv = ff_diskio_get_pdrv_wl(wl_handle);
|
||||
ESP_RETURN_ON_FALSE(pdrv != 0xff, ESP_ERR_INVALID_STATE, TAG, "partition isn't registered, call esp_vfs_fat_spiflash_mount_rw_wl first");
|
||||
|
||||
uint32_t id = FF_VOLUMES;
|
||||
ESP_RETURN_ON_FALSE(s_get_context_id_by_wl_handle(wl_handle, &id), ESP_ERR_INVALID_STATE, TAG, "partition isn't registered, call esp_vfs_fat_spiflash_mount_rw_wl first");
|
||||
//At this stage, as the wl_handle is valid, we should always get its context id, otherwise program should return already
|
||||
assert(id != FF_VOLUMES);
|
||||
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
ff_diskio_unregister(pdrv);
|
||||
ff_diskio_clear_pdrv_wl(wl_handle);
|
||||
// release partition driver
|
||||
esp_err_t err_drv = wl_unmount(wl_handle);
|
||||
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
|
||||
if (err == ESP_OK) {
|
||||
err = err_drv;
|
||||
}
|
||||
|
||||
free(s_ctx[id]);
|
||||
s_ctx[id] = NULL;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
bool partition_was_mounted = false;
|
||||
|
||||
wl_handle_t temp_handle = WL_INVALID_HANDLE;
|
||||
uint32_t id = FF_VOLUMES;
|
||||
char drv[3] = {0, ':', 0};
|
||||
|
||||
bool found = s_get_context_id_by_label(partition_label, &id);
|
||||
if (!found) {
|
||||
const esp_vfs_fat_mount_config_t mount_config = {
|
||||
.max_files = 1,
|
||||
.format_if_mount_failed = true,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition_label, &mount_config, &temp_handle), TAG, "Failed to mount");
|
||||
found = s_get_context_id_by_label(partition_label, &id);
|
||||
assert(found);
|
||||
} else {
|
||||
partition_was_mounted = true;
|
||||
}
|
||||
|
||||
//unmount
|
||||
drv[1] = (char)('0' + s_ctx[id]->pdrv);
|
||||
f_mount(0, drv, 0);
|
||||
|
||||
const size_t workbuf_size = 4096;
|
||||
void *workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto mount_back;
|
||||
}
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, s_ctx[id]->mount_config.allocation_unit_size);
|
||||
ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
|
||||
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size};
|
||||
FRESULT fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
|
||||
free(workbuf);
|
||||
workbuf = NULL;
|
||||
ESP_GOTO_ON_FALSE(fresult == FR_OK, ESP_FAIL, mount_back, TAG, "f_mkfs failed (%d)", fresult);
|
||||
|
||||
mount_back:
|
||||
if (partition_was_mounted) {
|
||||
esp_err_t err = s_f_mount_rw(s_ctx[id]->fs, drv, &s_ctx[id]->mount_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to mount back, go to recycle");
|
||||
goto recycle;
|
||||
}
|
||||
} else {
|
||||
esp_vfs_fat_spiflash_unmount_rw_wl(base_path, s_ctx[id]->wlhandle);
|
||||
}
|
||||
return ret;
|
||||
|
||||
recycle:
|
||||
ff_diskio_unregister(s_ctx[id]->pdrv);
|
||||
ff_diskio_clear_pdrv_wl(s_ctx[id]->wlhandle);
|
||||
wl_unmount(s_ctx[id]->wlhandle);
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
free(s_ctx[id]);
|
||||
s_ctx[id] = NULL;
|
||||
ESP_LOGE(TAG, "failed to format, resources recycled, please mount again");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
|
||||
ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
|
||||
ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
|
||||
|
||||
// connect driver to FATFS
|
||||
BYTE pdrv = 0xFF;
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
|
||||
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
ESP_GOTO_ON_ERROR(ff_diskio_register_raw_partition(pdrv, data_partition), fail, TAG, "ff_diskio_register_raw_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret);
|
||||
|
||||
FATFS *fs;
|
||||
ret = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
// it's okay, already registered with VFS
|
||||
} else if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Try to mount partition
|
||||
FRESULT fresult = f_mount(fs, drv, 1);
|
||||
if (fresult != FR_OK) {
|
||||
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
|
||||
ret = ESP_FAIL;
|
||||
goto fail;
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
ff_diskio_unregister(pdrv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* partition_label)
|
||||
{
|
||||
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
|
||||
ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
|
||||
ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
|
||||
|
||||
BYTE pdrv = ff_diskio_get_pdrv_raw(data_partition);
|
||||
ESP_RETURN_ON_FALSE(pdrv != 0xff, ESP_ERR_INVALID_STATE, TAG, "partition isn't registered, call esp_vfs_fat_spiflash_mount_ro first");
|
||||
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
ff_diskio_unregister(pdrv);
|
||||
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
wl_handle_t* wl_handle)
|
||||
__attribute__((alias("esp_vfs_fat_spiflash_mount_rw_wl")));
|
||||
esp_err_t esp_vfs_fat_spiflash_unmount(const char* base_path, wl_handle_t wl_handle)
|
||||
__attribute__((alias("esp_vfs_fat_spiflash_unmount_rw_wl")));
|
||||
esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path,
|
||||
const char* partition_label,
|
||||
const esp_vfs_fat_mount_config_t* mount_config)
|
||||
__attribute__((alias("esp_vfs_fat_spiflash_mount_ro")));
|
||||
esp_err_t esp_vfs_fat_rawflash_unmount(const char* base_path, const char* partition_label)
|
||||
__attribute__((alias("esp_vfs_fat_spiflash_unmount_ro")));
|
||||
Executable
+215
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from construct import Const, Int32ul, Struct
|
||||
from fatfs_utils.exceptions import WLNotInitialized
|
||||
from fatfs_utils.utils import (FULL_BYTE, UINT32_MAX, FATDefaults, crc32, generate_4bytes_random,
|
||||
get_args_for_partition_generator)
|
||||
from fatfsgen import FATFS
|
||||
|
||||
|
||||
def remove_wl(binary_image: bytes) -> bytes:
|
||||
partition_size: int = len(binary_image)
|
||||
total_sectors: int = partition_size // FATDefaults.WL_SECTOR_SIZE
|
||||
wl_state_size: int = WLFATFS.WL_STATE_HEADER_SIZE + WLFATFS.WL_STATE_RECORD_SIZE * total_sectors
|
||||
wl_state_sectors_cnt: int = (wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE
|
||||
wl_state_total_size: int = wl_state_sectors_cnt * FATDefaults.WL_SECTOR_SIZE
|
||||
wl_sectors_size: int = (wl_state_sectors_cnt
|
||||
* FATDefaults.WL_SECTOR_SIZE
|
||||
* WLFATFS.WL_STATE_COPY_COUNT
|
||||
+ FATDefaults.WL_SECTOR_SIZE)
|
||||
|
||||
correct_wl_configuration = binary_image[-wl_sectors_size:]
|
||||
|
||||
data_ = WLFATFS.WL_STATE_T_DATA.parse(correct_wl_configuration[:WLFATFS.WL_STATE_HEADER_SIZE])
|
||||
|
||||
total_records = 0
|
||||
# iterating over records field of the first copy of the state sector
|
||||
for i in range(WLFATFS.WL_STATE_HEADER_SIZE, wl_state_total_size, WLFATFS.WL_STATE_RECORD_SIZE):
|
||||
if correct_wl_configuration[i:i + WLFATFS.WL_STATE_RECORD_SIZE] != WLFATFS.WL_STATE_RECORD_SIZE * b'\xff':
|
||||
total_records += 1
|
||||
else:
|
||||
break
|
||||
before_dummy = binary_image[:total_records * FATDefaults.WL_SECTOR_SIZE]
|
||||
after_dummy = binary_image[total_records * FATDefaults.WL_SECTOR_SIZE + FATDefaults.WL_SECTOR_SIZE:]
|
||||
new_image: bytes = before_dummy + after_dummy
|
||||
|
||||
# remove wl sectors
|
||||
new_image = new_image[:len(new_image) - (FATDefaults.WL_SECTOR_SIZE + 2 * wl_state_total_size)]
|
||||
|
||||
# reorder to preserve original order
|
||||
new_image = (new_image[-data_['move_count'] * FATDefaults.WL_SECTOR_SIZE:]
|
||||
+ new_image[:-data_['move_count'] * FATDefaults.WL_SECTOR_SIZE])
|
||||
return new_image
|
||||
|
||||
|
||||
class WLFATFS:
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
WL_CFG_SECTORS_COUNT = 1
|
||||
WL_DUMMY_SECTORS_COUNT = 1
|
||||
WL_CONFIG_HEADER_SIZE = 48
|
||||
WL_STATE_RECORD_SIZE = 16
|
||||
WL_STATE_HEADER_SIZE = 64
|
||||
WL_STATE_COPY_COUNT = 2 # always 2 copies for power failure safety
|
||||
WL_SECTOR_SIZE = 0x1000
|
||||
|
||||
WL_STATE_T_DATA = Struct(
|
||||
'pos' / Int32ul,
|
||||
'max_pos' / Int32ul,
|
||||
'move_count' / Int32ul,
|
||||
'access_count' / Int32ul,
|
||||
'max_count' / Int32ul,
|
||||
'block_size' / Int32ul,
|
||||
'version' / Int32ul,
|
||||
'device_id' / Int32ul,
|
||||
'reserved' / Const(28 * b'\x00')
|
||||
)
|
||||
|
||||
WL_CONFIG_T_DATA = Struct(
|
||||
'start_addr' / Int32ul,
|
||||
'full_mem_size' / Int32ul,
|
||||
'page_size' / Int32ul,
|
||||
'sector_size' / Int32ul, # always 4096 for the types of NOR flash supported by ESP-IDF!
|
||||
'updaterate' / Int32ul,
|
||||
'wr_size' / Int32ul,
|
||||
'version' / Int32ul,
|
||||
'temp_buff_size' / Int32ul
|
||||
)
|
||||
WL_CONFIG_T_HEADER_SIZE = 48
|
||||
|
||||
def __init__(self,
|
||||
size: int = FATDefaults.SIZE,
|
||||
sector_size: int = FATDefaults.SECTOR_SIZE,
|
||||
reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT,
|
||||
fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT,
|
||||
sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER,
|
||||
explicit_fat_type: int = None,
|
||||
hidden_sectors: int = FATDefaults.HIDDEN_SECTORS,
|
||||
long_names_enabled: bool = False,
|
||||
num_heads: int = FATDefaults.NUM_HEADS,
|
||||
oem_name: str = FATDefaults.OEM_NAME,
|
||||
sec_per_track: int = FATDefaults.SEC_PER_TRACK,
|
||||
volume_label: str = FATDefaults.VOLUME_LABEL,
|
||||
file_sys_type: str = FATDefaults.FILE_SYS_TYPE,
|
||||
use_default_datetime: bool = True,
|
||||
version: int = FATDefaults.VERSION,
|
||||
temp_buff_size: int = FATDefaults.TEMP_BUFFER_SIZE,
|
||||
device_id: int = None,
|
||||
root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT,
|
||||
media_type: int = FATDefaults.MEDIA_TYPE) -> None:
|
||||
self._initialized = False
|
||||
self._version = version
|
||||
self._temp_buff_size = temp_buff_size
|
||||
self._device_id = device_id
|
||||
self.partition_size = size
|
||||
self.total_sectors = self.partition_size // FATDefaults.WL_SECTOR_SIZE
|
||||
self.wl_state_size = WLFATFS.WL_STATE_HEADER_SIZE + WLFATFS.WL_STATE_RECORD_SIZE * self.total_sectors
|
||||
|
||||
# determine the number of required sectors (roundup to sector size)
|
||||
self.wl_state_sectors = (self.wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE
|
||||
|
||||
self.boot_sector_start = FATDefaults.WL_SECTOR_SIZE # shift by one "dummy" sector
|
||||
self.fat_table_start = self.boot_sector_start + reserved_sectors_cnt * FATDefaults.WL_SECTOR_SIZE
|
||||
|
||||
wl_sectors = (WLFATFS.WL_DUMMY_SECTORS_COUNT + WLFATFS.WL_CFG_SECTORS_COUNT +
|
||||
self.wl_state_sectors * WLFATFS.WL_STATE_COPY_COUNT)
|
||||
self.plain_fat_sectors = self.total_sectors - wl_sectors
|
||||
self.plain_fatfs = FATFS(
|
||||
explicit_fat_type=explicit_fat_type,
|
||||
size=self.plain_fat_sectors * FATDefaults.WL_SECTOR_SIZE,
|
||||
reserved_sectors_cnt=reserved_sectors_cnt,
|
||||
fat_tables_cnt=fat_tables_cnt,
|
||||
sectors_per_cluster=sectors_per_cluster,
|
||||
sector_size=sector_size,
|
||||
root_entry_count=root_entry_count,
|
||||
hidden_sectors=hidden_sectors,
|
||||
long_names_enabled=long_names_enabled,
|
||||
num_heads=num_heads,
|
||||
use_default_datetime=use_default_datetime,
|
||||
oem_name=oem_name,
|
||||
sec_per_track=sec_per_track,
|
||||
volume_label=volume_label,
|
||||
file_sys_type=file_sys_type,
|
||||
media_type=media_type
|
||||
)
|
||||
|
||||
self.fatfs_binary_image = self.plain_fatfs.state.binary_image
|
||||
|
||||
def init_wl(self) -> None:
|
||||
self.fatfs_binary_image = self.plain_fatfs.state.binary_image
|
||||
self._add_dummy_sector()
|
||||
# config must be added after state, do not change the order of these two calls!
|
||||
self._add_state_sectors()
|
||||
self._add_config_sector()
|
||||
self._initialized = True
|
||||
|
||||
def _add_dummy_sector(self) -> None:
|
||||
self.fatfs_binary_image = FATDefaults.WL_SECTOR_SIZE * FULL_BYTE + self.fatfs_binary_image
|
||||
|
||||
def _add_config_sector(self) -> None:
|
||||
wl_config_data = WLFATFS.WL_CONFIG_T_DATA.build(
|
||||
dict(
|
||||
start_addr=0,
|
||||
full_mem_size=self.partition_size,
|
||||
page_size=FATDefaults.WL_SECTOR_SIZE, # equal to sector size (always 4096)
|
||||
sector_size=FATDefaults.WL_SECTOR_SIZE,
|
||||
updaterate=FATDefaults.UPDATE_RATE,
|
||||
wr_size=FATDefaults.WR_SIZE,
|
||||
version=self._version,
|
||||
temp_buff_size=self._temp_buff_size
|
||||
)
|
||||
)
|
||||
|
||||
crc = crc32(list(wl_config_data), UINT32_MAX)
|
||||
wl_config_crc = Int32ul.build(crc)
|
||||
|
||||
# adding three 4 byte zeros to align the structure
|
||||
wl_config = wl_config_data + wl_config_crc + Int32ul.build(0) + Int32ul.build(0) + Int32ul.build(0)
|
||||
|
||||
self.fatfs_binary_image += (
|
||||
wl_config + (FATDefaults.WL_SECTOR_SIZE - WLFATFS.WL_CONFIG_HEADER_SIZE) * FULL_BYTE)
|
||||
|
||||
def _add_state_sectors(self) -> None:
|
||||
wl_state_data = WLFATFS.WL_STATE_T_DATA.build(
|
||||
dict(
|
||||
pos=0,
|
||||
max_pos=self.plain_fat_sectors + WLFATFS.WL_DUMMY_SECTORS_COUNT,
|
||||
move_count=0,
|
||||
access_count=0,
|
||||
max_count=FATDefaults.UPDATE_RATE,
|
||||
block_size=FATDefaults.WL_SECTOR_SIZE, # equal to page size, thus equal to wl sector size (4096)
|
||||
version=self._version,
|
||||
device_id=self._device_id or generate_4bytes_random(),
|
||||
)
|
||||
)
|
||||
crc = crc32(list(wl_state_data), UINT32_MAX)
|
||||
wl_state_crc = Int32ul.build(crc)
|
||||
wl_state = wl_state_data + wl_state_crc
|
||||
wl_state_sector_padding: bytes = (FATDefaults.WL_SECTOR_SIZE - WLFATFS.WL_STATE_HEADER_SIZE) * FULL_BYTE
|
||||
wl_state_sector: bytes = (
|
||||
wl_state + wl_state_sector_padding + (self.wl_state_sectors - 1) * FATDefaults.WL_SECTOR_SIZE * FULL_BYTE
|
||||
)
|
||||
self.fatfs_binary_image += (WLFATFS.WL_STATE_COPY_COUNT * wl_state_sector)
|
||||
|
||||
def wl_write_filesystem(self, output_path: str) -> None:
|
||||
if not self._initialized:
|
||||
raise WLNotInitialized('FATFS is not initialized with WL. First call method WLFATFS.init_wl!')
|
||||
with open(output_path, 'wb') as output:
|
||||
output.write(bytearray(self.fatfs_binary_image))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
desc = 'Create a FAT filesystem with support for wear levelling and populate it with directory content'
|
||||
args = get_args_for_partition_generator(desc, wl=True)
|
||||
wl_fatfs = WLFATFS(sectors_per_cluster=args.sectors_per_cluster,
|
||||
size=args.partition_size,
|
||||
sector_size=args.sector_size,
|
||||
root_entry_count=args.root_entry_count,
|
||||
explicit_fat_type=args.fat_type,
|
||||
long_names_enabled=args.long_name_support,
|
||||
use_default_datetime=args.use_default_datetime)
|
||||
|
||||
wl_fatfs.plain_fatfs.generate(args.input_directory)
|
||||
wl_fatfs.init_wl()
|
||||
wl_fatfs.wl_write_filesystem(args.output_file)
|
||||
Reference in New Issue
Block a user