From 9b6e7484d0ca71f9a0b6b494a51f35474d2fc61d Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Fri, 25 Sep 2026 18:10:43 -0700 Subject: [PATCH 1/3] companion: Lock InternalFS during ExtraFS flash operations On nRF52 boards with EXTRAFS, InternalFS and ExtraFS both share the same flash cache, which has no locking, and the two filesystems each had their own mutex. Thus Bluefruit could write bonds to InternalFS from its task while the main loop read and wrote ExtraFS. During testing of a custom client, permanent BLE connection failure was observed after around ten connections. The problem was traced to a request for battery level (which includes filesystem state for free) that appeared to occasionally corrupt the flash. Add a LockedLFS wrapper around CustomLFS so ExtraFS operations go through InternalFS's lock. The lock order is ExtraFS then InternalFS. After this fix, a run of 100 connections was made with no BLE failure. --- examples/companion_radio/LockedLFS.h | 52 ++++++++++++++++++++++++++++ examples/companion_radio/main.cpp | 4 +-- 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 examples/companion_radio/LockedLFS.h diff --git a/examples/companion_radio/LockedLFS.h b/examples/companion_radio/LockedLFS.h new file mode 100644 index 0000000000..56eb0a2090 --- /dev/null +++ b/examples/companion_radio/LockedLFS.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +// InternalFS and a CustomLFS on internal flash use the same flash cache, which +// has no lock of its own. InternalFS uses it under its own lock, so take that +// lock around all operations here too. +class LockedLFS : public CustomLFS { + lfs_config orig; + + static const lfs_config& base(const lfs_config* c) { + return static_cast(static_cast(c->context))->orig; + } + + static int read(const lfs_config* c, lfs_block_t b, lfs_off_t o, void* buf, lfs_size_t n) { + InternalFS._lockFS(); + int r = base(c).read(c, b, o, buf, n); + InternalFS._unlockFS(); + return r; + } + + static int prog(const lfs_config* c, lfs_block_t b, lfs_off_t o, const void* buf, lfs_size_t n) { + InternalFS._lockFS(); + int r = base(c).prog(c, b, o, buf, n); + InternalFS._unlockFS(); + return r; + } + + static int erase(const lfs_config* c, lfs_block_t b) { + InternalFS._lockFS(); + int r = base(c).erase(c, b); + InternalFS._unlockFS(); + return r; + } + + static int sync(const lfs_config* c) { + InternalFS._lockFS(); + int r = base(c).sync(c); + InternalFS._unlockFS(); + return r; + } + +public: + LockedLFS(uint32_t addr, uint32_t size, uint32_t block) + : CustomLFS(addr, size, block), orig(_lfs_config) { + _lfs_config.read = read; + _lfs_config.prog = prog; + _lfs_config.erase = erase; + _lfs_config.sync = sync; + } +}; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 839d495833..845f719ade 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -91,8 +91,8 @@ MultiSerialInterface interface_manager; DataStore store(InternalFS, QSPIFlash, rtc_clock); #else #if defined(EXTRAFS) - #include - CustomLFS ExtraFS(0xD4000, 0x19000, 128); + #include "LockedLFS.h" + LockedLFS ExtraFS(0xD4000, 0x19000, 128); DataStore store(InternalFS, ExtraFS, rtc_clock); #else DataStore store(InternalFS, rtc_clock); From 2edb686260dbd47cde56a3049894bf78c159c79f Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Fri, 25 Sep 2026 18:26:30 -0700 Subject: [PATCH 2/3] companion: Lock the filesystem around lfs_traverse CMD_GET_BATT_AND_STORAGE computes used storage with lfs_traverse on the raw lfs_t from _getFS(). Adafruit_LittleFS takes its mutex around all of its own LittleFS calls, but this call skips it. LittleFS is not thread safe, so this call should be synchronized with the rest of them. --- examples/companion_radio/DataStore.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 06c56a7a44..95ea5383d1 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -91,7 +91,9 @@ int _countLfsBlock(void *p, lfs_block_t block){ lfs_ssize_t _getLfsUsedBlockCount(FILESYSTEM* fs) { lfs_size_t size = 0; + fs->_lockFS(); int err = lfs_traverse(fs->_getFS(), _countLfsBlock, &size); + fs->_unlockFS(); if (err) { MESH_DEBUG_PRINTLN("ERROR: lfs_traverse() error: %d", err); return 0; From a37370a570c46808e1d4b7471a99ead162d32ac7 Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Fri, 25 Sep 2026 18:29:13 -0700 Subject: [PATCH 3/3] companion: Fix off-by-one in the lfs_traverse block bound check _countLfsBlock rejects blocks greater than the filesystem's block count, but valid blocks run from 0 to count - 1, so a block equal to the count was let through. --- examples/companion_radio/DataStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 95ea5383d1..60f719b9c7 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -80,7 +80,7 @@ void DataStore::begin() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) int _countLfsBlock(void *p, lfs_block_t block){ - if (block > _ContactsChannelsTotalBlocks) { + if (block >= _ContactsChannelsTotalBlocks) { MESH_DEBUG_PRINTLN("ERROR: Block %d exceeds filesystem bounds - CORRUPTION DETECTED!", block); return LFS_ERR_CORRUPT; // return error to abort lfs_traverse() gracefully }