parent
9287c4eb8c
commit
7b72e5479e
@ -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 |
@ -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() |
@ -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 |
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,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,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,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,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); |
||||||
|
} |
@ -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() |
@ -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() |
@ -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"))); |
@ -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) |
Loading…
Reference in new issue