Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cc8bffd
stm32wl: check HAL_FLASH_Unlock() return in _internal_flash_erase
ndoo Apr 9, 2026
b4f90f0
stm32wl: fix _internal_flash_prog to abort on first write error
ndoo Apr 9, 2026
d40d5af
stm32wl: clear stale flash SR error flags before erase and program
ndoo Apr 9, 2026
6af98d7
stm32wl: reject flash prog writes not aligned to 8-byte doubleword
ndoo Apr 9, 2026
3fa5a0d
fix(nrf52,fs): use atomic SafeFile rename instead of direct write
ndoo Apr 15, 2026
05956cc
fix(admin): skip uiconfig.proto save on devices without a screen
ndoo Apr 15, 2026
5623be6
fs: enable format-on-retry for all platforms in saveToDisk
ndoo Apr 15, 2026
1c07ea0
DO NOT MERGE: nrf52(fs): add File() default constructor bound to Inte…
ndoo Apr 15, 2026
ff6cdad
stm32wl(fs): add File() default constructor and document LFS tunables
ndoo Apr 15, 2026
81e85af
fs: remove arch-specific ifdefs from FSCommon, SafeFile, xmodem
ndoo Apr 15, 2026
f5f29f3
stm32wl(fs): add write-behind page cache, reduce virtual block size a…
ndoo Apr 15, 2026
1bf2b69
Merge branch 'develop' into feat/stm32-lfs-cleanup
thebentern Apr 23, 2026
ee5d8b4
Merge branch 'develop' into feat/stm32-lfs-cleanup
thebentern Apr 23, 2026
7151ed4
stm32wl(fs): reduce FS reservation from 10 pages to 7 pages (FORMAT B…
ndoo Apr 25, 2026
3dc3948
fix(fs): return false in renameFile() when FSCom is not defined
ndoo Apr 25, 2026
b8d368f
Merge branch 'develop' into feat/stm32-lfs-cleanup
thebentern Apr 25, 2026
12a75cc
Merge branch 'develop' into feat/stm32-lfs-cleanup
thebentern May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 58 additions & 90 deletions src/FSCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,28 +79,44 @@ bool copyFile(const char *from, const char *to)
bool renameFile(const char *pathFrom, const char *pathTo)
{
#ifdef FSCom

#ifdef ARCH_ESP32
// take SPI Lock
spiLock->lock();
// rename was fixed for ESP32 IDF LittleFS in April
bool result = FSCom.rename(pathFrom, pathTo);
spiLock->unlock();
return result;
Comment thread
ndoo marked this conversation as resolved.
#else
// copyFile does its own locking.
if (copyFile(pathFrom, pathTo) && FSCom.remove(pathFrom)) {
return true;
} else {
return false;
}
#endif

#endif
}

#include <vector>

/**
* @brief Platform-agnostic filesystem format / wipe.
*
* On embedded targets (ESP32, NRF52, STM32WL, RP2040) this calls the
* native FSCom.format() which erases and reinitialises the LittleFS
* partition.
*
* On Portduino the fs::FS backend has no format() method. We instead
* delete /prefs (the only meshtastic data directory written at runtime)
* and return. rmDir("/prefs") is already called unconditionally by
* factoryReset() so this is a proven primitive on Portduino.
* FSBegin() is a no-op (#define FSBegin() true) on Portduino.
*
* @return true on success, false on failure or if no filesystem is configured.
*/
bool fsFormat()
{
#ifdef FSCom
#if defined(ARCH_PORTDUINO)
rmDir("/prefs");
return FSBegin();
#else
return FSCom.format();
#endif
#else
return false;
#endif
}

/**
* @brief Get the list of files in a directory.
*
Expand All @@ -123,23 +139,21 @@ std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)

File file = root.openNextFile();
while (file) {
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
#ifdef ARCH_ESP32
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(file.path(), levels - 1);
const char *filepath = file.path();
#else
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(file.name(), levels - 1);
const char *filepath = file.name();
#endif
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(filepath, levels - 1);
filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end());
file.close();
}
} else {
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
#ifdef ARCH_ESP32
strcpy(fileInfo.file_name, file.path());
#else
strcpy(fileInfo.file_name, file.name());
#endif
strncpy(fileInfo.file_name, filepath, sizeof(fileInfo.file_name) - 1);
fileInfo.file_name[sizeof(fileInfo.file_name) - 1] = '\0';
if (!String(fileInfo.file_name).endsWith(".")) {
Comment thread
thebentern marked this conversation as resolved.
filenames.push_back(fileInfo);
}
Expand All @@ -163,98 +177,59 @@ std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
void listDir(const char *dirname, uint8_t levels, bool del)
{
#ifdef FSCom
#if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
char buffer[255];
#endif
File root = FSCom.open(dirname, FILE_O_READ);
if (!root) {
return;
}
if (!root.isDirectory()) {
if (!root || !root.isDirectory())
return;
}

File file = root.openNextFile();
while (
file &&
file.name()[0]) { // This file.name() check is a workaround for a bug in the Adafruit LittleFS nrf52 glue (see issue 4395)
while (file && file.name()[0]) { // file.name()[0] check: workaround for Adafruit LittleFS nRF52 bug #4395
#ifdef ARCH_ESP32
const char *filepath = file.path();
#else
const char *filepath = file.name();
#endif
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
#ifdef ARCH_ESP32
listDir(file.path(), levels - 1, del);
listDir(filepath, levels - 1, del);
if (del) {
LOG_DEBUG("Remove %s", file.path());
strncpy(buffer, file.path(), sizeof(buffer));
LOG_DEBUG("Remove %s", filepath);
strncpy(buffer, filepath, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
file.close();
FSCom.rmdir(buffer);
} else {
Comment thread
thebentern marked this conversation as resolved.
file.close();
}
#elif (defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
listDir(file.name(), levels - 1, del);
if (del) {
LOG_DEBUG("Remove %s", file.name());
strncpy(buffer, file.name(), sizeof(buffer));
file.close();
FSCom.rmdir(buffer);
} else {
file.close();
}
#else
LOG_DEBUG(" %s (directory)", file.name());
listDir(file.name(), levels - 1, del);
file.close();
#endif
}
} else {
#ifdef ARCH_ESP32
if (del) {
LOG_DEBUG("Delete %s", file.path());
strncpy(buffer, file.path(), sizeof(buffer));
file.close();
FSCom.remove(buffer);
} else {
LOG_DEBUG(" %s (%i Bytes)", file.path(), file.size());
file.close();
}
#elif (defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
if (del) {
LOG_DEBUG("Delete %s", file.name());
strncpy(buffer, file.name(), sizeof(buffer));
LOG_DEBUG("Delete %s", filepath);
strncpy(buffer, filepath, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
file.close();
FSCom.remove(buffer);
Comment thread
ndoo marked this conversation as resolved.
} else {
Comment thread
thebentern marked this conversation as resolved.
LOG_DEBUG(" %s (%i Bytes)", file.name(), file.size());
LOG_DEBUG(" %s (%i Bytes)", filepath, file.size());
file.close();
}
#else
LOG_DEBUG(" %s (%i Bytes)", file.name(), file.size());
file.close();
#endif
}
file = root.openNextFile();
}
#ifdef ARCH_ESP32
const char *rootpath = root.path();
#else
const char *rootpath = root.name();
#endif
if (del) {
LOG_DEBUG("Remove %s", root.path());
strncpy(buffer, root.path(), sizeof(buffer));
root.close();
FSCom.rmdir(buffer);
} else {
root.close();
}
#elif (defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
if (del) {
LOG_DEBUG("Remove %s", root.name());
strncpy(buffer, root.name(), sizeof(buffer));
LOG_DEBUG("Remove %s", rootpath);
strncpy(buffer, rootpath, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
root.close();
FSCom.rmdir(buffer);
Comment thread
thebentern marked this conversation as resolved.
} else {
root.close();
}
#else
root.close();
#endif
#endif
}

Expand All @@ -268,14 +243,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
void rmDir(const char *dirname)
{
#ifdef FSCom

#if (defined(ARCH_ESP32) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
listDir(dirname, 10, true);
#elif defined(ARCH_NRF52)
// nRF52 implementation of LittleFS has a recursive delete function
FSCom.rmdir_r(dirname);
#endif

#endif
}

Expand Down
1 change: 1 addition & 0 deletions src/FSCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ void fsInit();
void fsListFiles();
bool copyFile(const char *from, const char *to);
bool renameFile(const char *pathFrom, const char *pathTo);
bool fsFormat();
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
void listDir(const char *dirname, uint8_t levels, bool del = false);
void rmDir(const char *dirname);
Expand Down
7 changes: 0 additions & 7 deletions src/SafeFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ static File openFile(const char *filename, bool fullAtomic)
{
concurrency::LockGuard g(spiLock);
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
#ifdef ARCH_NRF52
FSCom.remove(filename);
return FSCom.open(filename, FILE_O_WRITE);
#endif
if (!fullAtomic) {
FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists)
}
Expand Down Expand Up @@ -67,9 +63,6 @@ bool SafeFile::close()
f.close();
spiLock->unlock();

#ifdef ARCH_NRF52
return true;
#endif
if (!testReadback())
return false;

Expand Down
6 changes: 2 additions & 4 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@ void NodeDB::loadFromDisk()
spiLock->lock();
if (!FSCom.exists("/prefs/" xstr(BUILD_EPOCH))) {
LOG_WARN("Factory Install Reset!");
FSCom.format();
fsFormat();
FSCom.mkdir("/prefs");
File f2 = FSCom.open("/prefs/" xstr(BUILD_EPOCH), FILE_O_WRITE);
if (f2) {
Expand Down Expand Up @@ -1611,12 +1611,10 @@ bool NodeDB::saveToDisk(int saveWhat)

if (!success) {
LOG_ERROR("Failed to save to disk, retrying");
#ifdef ARCH_NRF52 // @geeksville is not ready yet to say we should do this on other platforms. See bug #4184 discussion
spiLock->lock();
FSCom.format();
fsFormat();
spiLock->unlock();

#endif
success = saveToDiskNoRetry(saveWhat);

RECORD_CRITICALERROR(success ? meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE
Expand Down
2 changes: 2 additions & 0 deletions src/modules/AdminModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,9 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot)

void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg)
{
#if HAS_SCREEN
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uicfg);
#endif
}

void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
Expand Down
Loading
Loading