Merge branch 'feature/add-wl-support-fatfsparse' into 'master'

fatfsparse.py: Add support for WL

Closes IDF-4994 and IDF-5522

See merge request espressif/esp-idf!18760
This commit is contained in:
Ivan Grokhotkov
2022-07-27 16:01:02 +08:00
20 changed files with 260 additions and 44 deletions

View File

@@ -47,7 +47,7 @@ class Entry:
'DIR_Name' / PaddedString(MAX_NAME_SIZE, SHORT_NAMES_ENCODING), 'DIR_Name' / PaddedString(MAX_NAME_SIZE, SHORT_NAMES_ENCODING),
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, SHORT_NAMES_ENCODING), 'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, SHORT_NAMES_ENCODING),
'DIR_Attr' / Int8ul, 'DIR_Attr' / Int8ul,
'DIR_NTRes' / Const(EMPTY_BYTE), '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_CrtTimeTenth' / Const(EMPTY_BYTE), # ignored by esp-idf fatfs library
'DIR_CrtTime' / Int16ul, # 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_CrtDate' / Int16ul, # ignored by esp-idf fatfs library
@@ -159,6 +159,7 @@ class Entry:
lfn_order: int = SHORT_ENTRY, lfn_order: int = SHORT_ENTRY,
lfn_names: Optional[List[bytes]] = None, lfn_names: Optional[List[bytes]] = None,
lfn_checksum_: int = 0, lfn_checksum_: int = 0,
fits_short: bool = False,
lfn_is_last: bool = False) -> None: lfn_is_last: bool = False) -> None:
""" """
:param first_cluster_id: id of the first data cluster for given entry :param first_cluster_id: id of the first data cluster for given entry
@@ -172,6 +173,7 @@ class Entry:
:param lfn_names: if the entry is dedicated for long names the lfn_names contains :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 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 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, :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 thus its address is first in the physical order
:returns: None :returns: None
@@ -213,6 +215,7 @@ class Entry:
DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE), DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE),
DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE), DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE),
DIR_Attr=entity_type, 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_FstClusLO=first_cluster_id,
DIR_FileSize=size, DIR_FileSize=size,
DIR_CrtDate=date_entry_, # ignored by esp-idf fatfs library DIR_CrtDate=date_entry_, # ignored by esp-idf fatfs library

View File

@@ -260,6 +260,7 @@ class Directory:
entity_extension=extension, entity_extension=extension,
date=fatfs_date_, date=fatfs_date_,
time=fatfs_time_, time=fatfs_time_,
fits_short=True,
entity_type=entity_type) entity_type=entity_type)
return free_cluster, free_entry, target_dir return free_cluster, free_entry, target_dir
return self.allocate_long_name_object(free_entry=free_entry, return self.allocate_long_name_object(free_entry=free_entry,

View File

@@ -42,7 +42,7 @@ FATFS_SECONDS_GRANULARITY: int = 2
LONG_NAMES_ENCODING: str = 'utf-16' LONG_NAMES_ENCODING: str = 'utf-16'
SHORT_NAMES_ENCODING: str = 'utf-8' SHORT_NAMES_ENCODING: str = 'utf-8'
ALLOWED_SECTOR_SIZES: List[int] = [512, 1024, 2048, 4096] ALLOWED_SECTOR_SIZES: List[int] = [4096]
ALLOWED_SECTORS_PER_CLUSTER: List[int] = [1, 2, 4, 8, 16, 32, 64, 128] ALLOWED_SECTORS_PER_CLUSTER: List[int] = [1, 2, 4, 8, 16, 32, 64, 128]
@@ -181,7 +181,7 @@ def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
parser.add_argument('--fat_type', parser.add_argument('--fat_type',
default=0, default=0,
type=int, type=int,
choices=[12, 16, 0], choices=[FAT12, FAT16, 0],
help=""" help="""
Type of fat. Select 12 for fat12, 16 for fat16. Don't set, or set to 0 for automatic 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. calculation using cluster size and partition size.
@@ -269,3 +269,5 @@ class FATDefaults:
VERSION: int = 2 VERSION: int = 2
TEMP_BUFFER_SIZE: int = 32 TEMP_BUFFER_SIZE: int = 32
UPDATE_RATE: int = 16 UPDATE_RATE: int = 16
WR_SIZE: int = 16
WL_SECTOR_SIZE: int = 4096

21
components/fatfs/fatfsparse.py Normal file → Executable file
View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import argparse import argparse
@@ -9,6 +10,7 @@ from fatfs_utils.entry import Entry
from fatfs_utils.fat import FAT from fatfs_utils.fat import FAT
from fatfs_utils.fatfs_state import BootSectorState 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 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: def build_file_name(name1: bytes, name2: bytes, name3: bytes) -> str:
@@ -42,7 +44,7 @@ def traverse_folder_tree(directory_bytes_: bytes,
name: str, name: str,
state_: BootSectorState, state_: BootSectorState,
fat_: FAT, fat_: FAT,
binary_array_: bytearray) -> None: binary_array_: bytes) -> None:
os.makedirs(name) os.makedirs(name)
assert len(directory_bytes_) % FATDefaults.ENTRY_SIZE == 0 assert len(directory_bytes_) % FATDefaults.ENTRY_SIZE == 0
@@ -89,9 +91,26 @@ if __name__ == '__main__':
argument_parser.add_argument('--long-name-support', argument_parser.add_argument('--long-name-support',
action='store_true', action='store_true',
help='Set flag to enable long names support.') help='Set flag to enable long names support.')
argument_parser.add_argument('--wear-leveling',
action='store_true',
help='Set flag to parse an image encoded using wear levelling.')
args = argument_parser.parse_args() args = argument_parser.parse_args()
fs = read_filesystem(args.input_image) 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.wear_leveling:
fs = remove_wl(fs)
boot_sector_ = BootSector() boot_sector_ = BootSector()
boot_sector_.parse_boot_sector(fs) boot_sector_.parse_boot_sector(fs)
fat = FAT(boot_sector_.boot_sector_state, init_=False) fat = FAT(boot_sector_.boot_sector_state, init_=False)

View File

@@ -294,7 +294,7 @@ class FatFSGen(unittest.TestCase):
fatfs.create_file('HELLO', extension='TXT') fatfs.create_file('HELLO', extension='TXT')
fatfs.write_filesystem(CFG['output_file']) fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x2000: 0x2019], b'HELLO TXT \x00\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!') 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: def test_lfn_short_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True) fatfs = fatfsgen.FATFS(long_names_enabled=True)
@@ -302,7 +302,7 @@ class FatFSGen(unittest.TestCase):
fatfs.write_content(path_from_root=['HELLO.TXT'], content=b'this is a test') fatfs.write_content(path_from_root=['HELLO.TXT'], content=b'this is a test')
fatfs.write_filesystem(CFG['output_file']) fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x2000: 0x2010], b'HELLO TXT \x00\x00\x00\x00') 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[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') self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
@@ -367,7 +367,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\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[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[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 \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') 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: def test_lfn_nested_long_empty(self) -> None:
@@ -410,7 +410,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\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[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[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 \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[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') self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')

View File

@@ -23,6 +23,7 @@ class FatFSGen(unittest.TestCase):
shutil.rmtree('output_data', ignore_errors=True) shutil.rmtree('output_data', ignore_errors=True)
shutil.rmtree('Espressif', ignore_errors=True) shutil.rmtree('Espressif', ignore_errors=True)
shutil.rmtree('testf', ignore_errors=True) shutil.rmtree('testf', ignore_errors=True)
shutil.rmtree('testf_wl', ignore_errors=True)
if os.path.exists('fatfs_image.img'): if os.path.exists('fatfs_image.img'):
os.remove('fatfs_image.img') os.remove('fatfs_image.img')
@@ -138,6 +139,15 @@ class FatFSGen(unittest.TestCase):
], stderr=STDOUT) ], stderr=STDOUT)
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif') 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', '--wear-leveling', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_deeper(self) -> None: def test_e2e_deeper(self) -> None:
folder_ = { folder_ = {
@@ -159,6 +169,7 @@ class FatFSGen(unittest.TestCase):
folder_ folder_
] ]
} }
generate_local_folder_structure(struct_, path_='.') generate_local_folder_structure(struct_, path_='.')
run([ run([
'python', 'python',
@@ -167,6 +178,15 @@ class FatFSGen(unittest.TestCase):
], stderr=STDOUT) ], stderr=STDOUT)
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif') 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', '--wear-leveling', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_deeper_large(self) -> None: def test_e2e_deeper_large(self) -> None:
folder_ = { folder_ = {
@@ -214,6 +234,15 @@ class FatFSGen(unittest.TestCase):
], stderr=STDOUT) ], stderr=STDOUT)
run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif') 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', '--wear-leveling', 'fatfs_image.img'], stderr=STDOUT)
assert compare_folders('testf', 'Espressif')
def test_e2e_very_deep(self) -> None: def test_e2e_very_deep(self) -> None:
folder_ = { folder_ = {

View File

@@ -105,9 +105,6 @@ class WLFatFSGen(unittest.TestCase):
fatfs.wl_create_directory('TESTFOLD') fatfs.wl_create_directory('TESTFOLD')
self.assertRaises(WLNotInitialized, fatfs.wl_write_filesystem, CFG['output_file']) self.assertRaises(WLNotInitialized, fatfs.wl_write_filesystem, CFG['output_file'])
def test_wrong_sector_size(self) -> None:
self.assertRaises(NotImplementedError, wl_fatfsgen.WLFATFS, sector_size=1024)
def test_e2e_deep_folder_into_image_ext(self) -> None: def test_e2e_deep_folder_into_image_ext(self) -> None:
fatfs = wl_fatfsgen.WLFATFS() fatfs = wl_fatfsgen.WLFATFS()
fatfs.wl_generate(CFG['test_dir2']) fatfs.wl_generate(CFG['test_dir2'])

View File

@@ -11,6 +11,41 @@ from fatfs_utils.utils import (FULL_BYTE, UINT32_MAX, FATDefaults, crc32, genera
from fatfsgen import FATFS 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: class WLFATFS:
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
WL_CFG_SECTORS_COUNT = 1 WL_CFG_SECTORS_COUNT = 1
@@ -18,7 +53,7 @@ class WLFATFS:
WL_CONFIG_HEADER_SIZE = 48 WL_CONFIG_HEADER_SIZE = 48
WL_STATE_RECORD_SIZE = 16 WL_STATE_RECORD_SIZE = 16
WL_STATE_HEADER_SIZE = 64 WL_STATE_HEADER_SIZE = 64
WL_STATE_COPY_COUNT = 2 WL_STATE_COPY_COUNT = 2 # always 2 copies for power failure safety
WL_SECTOR_SIZE = 0x1000 WL_SECTOR_SIZE = 0x1000
WL_STATE_T_DATA = Struct( WL_STATE_T_DATA = Struct(
@@ -37,7 +72,7 @@ class WLFATFS:
'start_addr' / Int32ul, 'start_addr' / Int32ul,
'full_mem_size' / Int32ul, 'full_mem_size' / Int32ul,
'page_size' / Int32ul, 'page_size' / Int32ul,
'sector_size' / Int32ul, 'sector_size' / Int32ul, # always 4096 for the types of NOR flash supported by ESP-IDF!
'updaterate' / Int32ul, 'updaterate' / Int32ul,
'wr_size' / Int32ul, 'wr_size' / Int32ul,
'version' / Int32ul, 'version' / Int32ul,
@@ -50,7 +85,6 @@ class WLFATFS:
reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT, reserved_sectors_cnt: int = FATDefaults.RESERVED_SECTORS_COUNT,
fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT, fat_tables_cnt: int = FATDefaults.FAT_TABLES_COUNT,
sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER, sectors_per_cluster: int = FATDefaults.SECTORS_PER_CLUSTER,
sector_size: int = FATDefaults.SECTOR_SIZE,
sectors_per_fat: int = FATDefaults.SECTORS_PER_FAT, sectors_per_fat: int = FATDefaults.SECTORS_PER_FAT,
explicit_fat_type: int = None, explicit_fat_type: int = None,
hidden_sectors: int = FATDefaults.HIDDEN_SECTORS, hidden_sectors: int = FATDefaults.HIDDEN_SECTORS,
@@ -63,28 +97,22 @@ class WLFATFS:
use_default_datetime: bool = True, use_default_datetime: bool = True,
version: int = FATDefaults.VERSION, version: int = FATDefaults.VERSION,
temp_buff_size: int = FATDefaults.TEMP_BUFFER_SIZE, temp_buff_size: int = FATDefaults.TEMP_BUFFER_SIZE,
update_rate: int = FATDefaults.UPDATE_RATE,
device_id: int = None, device_id: int = None,
root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT, root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT,
media_type: int = FATDefaults.MEDIA_TYPE) -> None: media_type: int = FATDefaults.MEDIA_TYPE) -> None:
if sector_size != WLFATFS.WL_SECTOR_SIZE:
raise NotImplementedError(f'The only supported sector size is currently {WLFATFS.WL_SECTOR_SIZE}')
self._initialized = False self._initialized = False
self.sector_size = sector_size
self._version = version self._version = version
self._temp_buff_size = temp_buff_size self._temp_buff_size = temp_buff_size
self._device_id = device_id self._device_id = device_id
self._update_rate = update_rate
self.partition_size = size self.partition_size = size
self.total_sectors = self.partition_size // self.sector_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 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) # determine the number of required sectors (roundup to sector size)
self.wl_state_sectors = (self.wl_state_size + self.sector_size - 1) // self.sector_size self.wl_state_sectors = (self.wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE
self.boot_sector_start = self.sector_size # shift by one "dummy" sector self.boot_sector_start = FATDefaults.WL_SECTOR_SIZE # shift by one "dummy" sector
self.fat_table_start = self.boot_sector_start + reserved_sectors_cnt * self.sector_size 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 + wl_sectors = (WLFATFS.WL_DUMMY_SECTORS_COUNT + WLFATFS.WL_CFG_SECTORS_COUNT +
self.wl_state_sectors * WLFATFS.WL_STATE_COPY_COUNT) self.wl_state_sectors * WLFATFS.WL_STATE_COPY_COUNT)
@@ -92,11 +120,11 @@ class WLFATFS:
self.plain_fatfs = FATFS( self.plain_fatfs = FATFS(
explicit_fat_type=explicit_fat_type, explicit_fat_type=explicit_fat_type,
size=self.plain_fat_sectors * self.sector_size, size=self.plain_fat_sectors * FATDefaults.WL_SECTOR_SIZE,
reserved_sectors_cnt=reserved_sectors_cnt, reserved_sectors_cnt=reserved_sectors_cnt,
fat_tables_cnt=fat_tables_cnt, fat_tables_cnt=fat_tables_cnt,
sectors_per_cluster=sectors_per_cluster, sectors_per_cluster=sectors_per_cluster,
sector_size=sector_size, sector_size=FATDefaults.WL_SECTOR_SIZE,
sectors_per_fat=sectors_per_fat, sectors_per_fat=sectors_per_fat,
root_entry_count=root_entry_count, root_entry_count=root_entry_count,
hidden_sectors=hidden_sectors, hidden_sectors=hidden_sectors,
@@ -121,17 +149,17 @@ class WLFATFS:
self._initialized = True self._initialized = True
def _add_dummy_sector(self) -> None: def _add_dummy_sector(self) -> None:
self.fatfs_binary_image = self.sector_size * FULL_BYTE + self.fatfs_binary_image self.fatfs_binary_image = FATDefaults.WL_SECTOR_SIZE * FULL_BYTE + self.fatfs_binary_image
def _add_config_sector(self) -> None: def _add_config_sector(self) -> None:
wl_config_data = WLFATFS.WL_CONFIG_T_DATA.build( wl_config_data = WLFATFS.WL_CONFIG_T_DATA.build(
dict( dict(
start_addr=0, start_addr=0,
full_mem_size=self.partition_size, full_mem_size=self.partition_size,
page_size=self.sector_size, page_size=FATDefaults.WL_SECTOR_SIZE, # equal to sector size (always 4096)
sector_size=self.sector_size, sector_size=FATDefaults.WL_SECTOR_SIZE,
updaterate=self._update_rate, updaterate=FATDefaults.UPDATE_RATE,
wr_size=16, wr_size=FATDefaults.WR_SIZE,
version=self._version, version=self._version,
temp_buff_size=self._temp_buff_size temp_buff_size=self._temp_buff_size
) )
@@ -143,7 +171,8 @@ class WLFATFS:
# adding three 4 byte zeros to align the structure # 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) wl_config = wl_config_data + wl_config_crc + Int32ul.build(0) + Int32ul.build(0) + Int32ul.build(0)
self.fatfs_binary_image += (wl_config + (self.sector_size - WLFATFS.WL_CONFIG_HEADER_SIZE) * FULL_BYTE) self.fatfs_binary_image += (
wl_config + (FATDefaults.WL_SECTOR_SIZE - WLFATFS.WL_CONFIG_HEADER_SIZE) * FULL_BYTE)
def _add_state_sectors(self) -> None: def _add_state_sectors(self) -> None:
wl_state_data = WLFATFS.WL_STATE_T_DATA.build( wl_state_data = WLFATFS.WL_STATE_T_DATA.build(
@@ -152,8 +181,8 @@ class WLFATFS:
max_pos=self.plain_fat_sectors + WLFATFS.WL_DUMMY_SECTORS_COUNT, max_pos=self.plain_fat_sectors + WLFATFS.WL_DUMMY_SECTORS_COUNT,
move_count=0, move_count=0,
access_count=0, access_count=0,
max_count=self._update_rate, max_count=FATDefaults.UPDATE_RATE,
block_size=self.sector_size, block_size=FATDefaults.WL_SECTOR_SIZE, # equal to page size, thus equal to wl sector size (4096)
version=self._version, version=self._version,
device_id=self._device_id or generate_4bytes_random(), device_id=self._device_id or generate_4bytes_random(),
) )
@@ -161,9 +190,9 @@ class WLFATFS:
crc = crc32(list(wl_state_data), UINT32_MAX) crc = crc32(list(wl_state_data), UINT32_MAX)
wl_state_crc = Int32ul.build(crc) wl_state_crc = Int32ul.build(crc)
wl_state = wl_state_data + wl_state_crc wl_state = wl_state_data + wl_state_crc
wl_state_sector_padding: bytes = (self.sector_size - WLFATFS.WL_STATE_HEADER_SIZE) * FULL_BYTE wl_state_sector_padding: bytes = (FATDefaults.WL_SECTOR_SIZE - WLFATFS.WL_STATE_HEADER_SIZE) * FULL_BYTE
wl_state_sector: bytes = ( wl_state_sector: bytes = (
wl_state + wl_state_sector_padding + (self.wl_state_sectors - 1) * self.sector_size * FULL_BYTE 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) self.fatfs_binary_image += (WLFATFS.WL_STATE_COPY_COUNT * wl_state_sector)
@@ -193,8 +222,7 @@ if __name__ == '__main__':
desc = 'Create a FAT filesystem with support for wear levelling and populate it with directory content' desc = 'Create a FAT filesystem with support for wear levelling and populate it with directory content'
args = get_args_for_partition_generator(desc) args = get_args_for_partition_generator(desc)
wl_fatfs = WLFATFS(sector_size=args.sector_size, wl_fatfs = WLFATFS(sectors_per_cluster=args.sectors_per_cluster,
sectors_per_cluster=args.sectors_per_cluster,
size=args.partition_size, size=args.partition_size,
root_entry_count=args.root_entry_count, root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type, explicit_fat_type=args.fat_type,

View File

@@ -7,6 +7,13 @@ menu "Example Configuration"
If read-only mode is set, the generated fatfs image will be raw (without wear levelling support). If read-only mode is set, the generated fatfs image will be raw (without wear levelling support).
Otherwise it will support wear levelling that enables read-write mounting. Otherwise it will support wear levelling that enables read-write mounting.
config EXAMPLE_FATFS_WRITE_COUNT
int "Number of volumes"
default 1
range 1 600
help
Number of writes to the file (for testing purposes).
config EXAMPLE_FATFS_DEFAULT_DATETIME config EXAMPLE_FATFS_DEFAULT_DATETIME
bool "Default modification date and time for generated FATFS image" bool "Default modification date and time for generated FATFS image"
default n default n

View File

@@ -66,13 +66,17 @@ void app_main(void)
if (!EXAMPLE_FATFS_MODE_READ_ONLY){ if (!EXAMPLE_FATFS_MODE_READ_ONLY){
// Open file for reading // Open file for reading
ESP_LOGI(TAG, "Opening file"); ESP_LOGI(TAG, "Opening file");
FILE *f = fopen(device_filename, "wb"); FILE *f;
for(int i = 0; i < CONFIG_EXAMPLE_FATFS_WRITE_COUNT; i++){
f = fopen(device_filename, "wb");
if (f == NULL) { if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing"); ESP_LOGE(TAG, "Failed to open file for writing");
return; return;
} }
fprintf(f, "This is written by the device"); fprintf(f, "This is written by the device");
fclose(f); fclose(f);
}
ESP_LOGI(TAG, "File written"); ESP_LOGI(TAG, "File written");
// Open file for reading // Open file for reading

View File

@@ -1,13 +1,55 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import os
import re import re
import shutil
import sys
from datetime import datetime from datetime import datetime
from subprocess import STDOUT, run
from typing import List from typing import List
import pytest import pytest
from pytest_embedded import Dut from pytest_embedded import Dut
idf_path = os.environ['IDF_PATH'] # get value of IDF_PATH from environment
parttool_dir = os.path.join(idf_path, 'components', 'partition_table')
sys.path.append(parttool_dir)
from parttool import PartitionName, ParttoolTarget # noqa E402 # pylint: disable=C0413
def file_(x: str, content_: str = 'hey this is a test') -> dict:
return {
'type': 'file',
'name': x,
'content': content_
}
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()
# Example_GENERIC # Example_GENERIC
@pytest.mark.esp32 @pytest.mark.esp32
@@ -51,6 +93,7 @@ def test_examples_fatfsgen(config: str, dut: Dut) -> None:
filename_sn = 'sub/test.txt' filename_sn = 'sub/test.txt'
date_modified = datetime.today() date_modified = datetime.today()
date_default = datetime(1980, 1, 1) date_default = datetime(1980, 1, 1)
fatfs_parser_path = os.path.join(idf_path, 'components', 'fatfs', 'fatfsparse.py')
if config in ['test_read_write_partition_gen', 'test_read_write_partition_gen_default_dt']: if config in ['test_read_write_partition_gen', 'test_read_write_partition_gen_default_dt']:
filename = filename_sn filename = filename_sn
@@ -68,6 +111,32 @@ def test_examples_fatfsgen(config: str, dut: Dut) -> None:
'example: Unmounting FAT filesystem', 'example: Unmounting FAT filesystem',
'example: Done'], timeout) 'example: Done'], timeout)
target = ParttoolTarget(dut.port)
target.read_partition(PartitionName('storage'), 'temp.img')
run(['python', fatfs_parser_path, '--wear-leveling', 'temp.img'], stderr=STDOUT)
folder_ = {
'type': 'folder',
'name': 'SUB',
'content': [
file_('TEST.TXT', content_='this is test\n'),
]
}
struct_: dict = {
'type': 'folder',
'name': 'testf',
'content': [
file_('HELLO.TXT', content_='This is generated on the host\n'),
file_('INNER.TXT', content_='This is written by the device'),
folder_
]
}
generate_local_folder_structure(struct_, path_='.')
try:
assert compare_folders('testf', 'Espressif')
finally:
shutil.rmtree('Espressif', ignore_errors=True)
shutil.rmtree('testf', ignore_errors=True)
elif config in ['test_read_only_partition_gen', 'test_read_only_partition_gen_default_dt']: elif config in ['test_read_only_partition_gen', 'test_read_only_partition_gen_default_dt']:
filename = filename_sn filename = filename_sn
filename_expected = f'/spiflash/{filename}' filename_expected = f'/spiflash/{filename}'
@@ -79,6 +148,30 @@ def test_examples_fatfsgen(config: str, dut: Dut) -> None:
expect_all(['example: Read from file: \'this is test\'', expect_all(['example: Read from file: \'this is test\'',
'example: Unmounting FAT filesystem', 'example: Unmounting FAT filesystem',
'example: Done'], timeout) 'example: Done'], timeout)
target = ParttoolTarget(dut.port)
target.read_partition(PartitionName('storage'), 'temp.img')
run(['python', fatfs_parser_path, '--long-name-support', 'temp.img'], stderr=STDOUT)
folder_ = {
'type': 'folder',
'name': 'sublongnames',
'content': [
file_('testlongfilenames.txt', content_='this is test; long name it has\n'),
]
}
struct_ = {
'type': 'folder',
'name': 'testf',
'content': [
file_('hellolongname.txt', content_='This is generated on the host; long name it has\n'),
folder_
]
}
generate_local_folder_structure(struct_, path_='.')
try:
assert compare_folders('testf', 'Espressif')
finally:
shutil.rmtree('Espressif', ignore_errors=True)
shutil.rmtree('testf', ignore_errors=True)
elif config in ['test_read_write_partition_gen_ln', 'test_read_write_partition_gen_ln_default_dt']: elif config in ['test_read_write_partition_gen_ln', 'test_read_write_partition_gen_ln_default_dt']:
filename = filename_ln filename = filename_ln
@@ -107,3 +200,27 @@ def test_examples_fatfsgen(config: str, dut: Dut) -> None:
expect_all(['example: Read from file: \'this is test; long name it has\'', expect_all(['example: Read from file: \'this is test; long name it has\'',
'example: Unmounting FAT filesystem', 'example: Unmounting FAT filesystem',
'example: Done'], timeout) 'example: Done'], timeout)
target = ParttoolTarget(dut.port)
target.read_partition(PartitionName('storage'), 'temp.img')
run(['python', fatfs_parser_path, 'temp.img'], stderr=STDOUT)
folder_ = {
'type': 'folder',
'name': 'SUB',
'content': [
file_('TEST.TXT', content_='this is test\n'),
]
}
struct_ = {
'type': 'folder',
'name': 'testf',
'content': [
file_('HELLO.TXT', content_='This is generated on the host\n'),
folder_
]
}
generate_local_folder_structure(struct_, path_='.')
try:
assert compare_folders('testf', 'Espressif')
finally:
shutil.rmtree('Espressif', ignore_errors=True)
shutil.rmtree('testf', ignore_errors=True)

View File

@@ -2,3 +2,4 @@ CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y
CONFIG_FATFS_LFN_HEAP=n CONFIG_FATFS_LFN_HEAP=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_FATFS_LFN_NONE=y CONFIG_FATFS_LFN_NONE=y
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -3,3 +3,4 @@ CONFIG_FATFS_LFN_HEAP=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_FATFS_LFN_NONE=y CONFIG_FATFS_LFN_NONE=y
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -2,3 +2,4 @@ CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y
CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_LFN_NONE=n CONFIG_FATFS_LFN_NONE=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -3,3 +3,4 @@ CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_LFN_NONE=n CONFIG_FATFS_LFN_NONE=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -2,3 +2,4 @@ CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n
CONFIG_FATFS_LFN_HEAP=n CONFIG_FATFS_LFN_HEAP=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_FATFS_LFN_NONE=y CONFIG_FATFS_LFN_NONE=y
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -3,3 +3,4 @@ CONFIG_FATFS_LFN_HEAP=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_FATFS_LFN_NONE=y CONFIG_FATFS_LFN_NONE=y
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -2,3 +2,4 @@ CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n
CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_LFN_NONE=n CONFIG_FATFS_LFN_NONE=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -3,3 +3,4 @@ CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_LFN_NONE=n CONFIG_FATFS_LFN_NONE=n
CONFIG_FATFS_LFN_STACK=n CONFIG_FATFS_LFN_STACK=n
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y
CONFIG_EXAMPLE_FATFS_WRITE_COUNT=300

View File

@@ -8,6 +8,7 @@ components/espcoredump/test/test_espcoredump.py
components/espcoredump/test/test_espcoredump.sh components/espcoredump/test/test_espcoredump.sh
components/espcoredump/test_apps/build_espcoredump.sh components/espcoredump/test_apps/build_espcoredump.sh
components/fatfs/fatfsgen.py components/fatfs/fatfsgen.py
components/fatfs/fatfsparse.py
components/fatfs/test_fatfsgen/test_fatfsgen.py components/fatfs/test_fatfsgen/test_fatfsgen.py
components/fatfs/test_fatfsgen/test_fatfsparse.py components/fatfs/test_fatfsgen/test_fatfsparse.py
components/fatfs/test_fatfsgen/test_wl_fatfsgen.py components/fatfs/test_fatfsgen/test_wl_fatfsgen.py