Skip to content

Commit cffa18c

Browse files
committed
fs: serialize littlefs access with a recursive mutex
littlefs is not thread-safe, yet FS is called from three different FreeRTOS tasks: the SystemTask (fs.Init, controller loads at boot), the display task (settings and alarm saves from the settings screens, watchface resource loading) and the BLE host task (FSService performs full littlefs I/O inside GATT access callbacks, and DFU writes during a BLE file transfer can run while the user navigates settings screens). Nothing serializes these calls today. The SpiMaster semaphore only serializes individual bus transactions; the shared lfs_t state (caches, open-file list, lookahead) is unprotected, so a BLE filesystem transfer racing a settings save can corrupt filesystem state. Take a recursive mutex in every public FS method and expose a RAII FS::Lock so callers can hold the lock across multi-call sequences. The recursive type matters: a caller holding FS::Lock still goes through the public methods. Holding the lock across a sequence is needed wherever an open file handle must not race a Rename or Delete of the same file, since lfs_dir_commit invalidates such handles in the mlist. Cost: one FreeRTOS recursive mutex and one pointer, no API change. Callers that never overlapped are unaffected; overlapping callers now briefly wait instead of interleaving inside littlefs.
1 parent 8d7a04e commit cffa18c

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

src/components/fs/FS.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <cstring>
33
#include <littlefs/lfs.h>
44
#include <lvgl/lvgl.h>
5+
#include "nrf_assert.h"
56

67
using namespace Pinetime::Controllers;
78

@@ -26,9 +27,12 @@ FS::FS(Pinetime::Drivers::SpiNorFlash& driver)
2627
.name_max = 50,
2728
.attr_max = 50,
2829
} {
30+
mutex = xSemaphoreCreateRecursiveMutex();
31+
ASSERT(mutex != nullptr);
2932
}
3033

3134
void FS::Init() {
35+
Lock lock(*this);
3236

3337
// try mount
3438
int err = lfs_mount(&lfs, &lfsConfig);
@@ -54,58 +58,72 @@ void FS::VerifyResource() {
5458
}
5559

5660
int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
61+
Lock lock(*this);
5762
return lfs_file_open(&lfs, file_p, fileName, flags);
5863
}
5964

6065
int FS::FileClose(lfs_file_t* file_p) {
66+
Lock lock(*this);
6167
return lfs_file_close(&lfs, file_p);
6268
}
6369

6470
int FS::FileRead(lfs_file_t* file_p, uint8_t* buff, uint32_t size) {
71+
Lock lock(*this);
6572
return lfs_file_read(&lfs, file_p, buff, size);
6673
}
6774

6875
int FS::FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size) {
76+
Lock lock(*this);
6977
return lfs_file_write(&lfs, file_p, buff, size);
7078
}
7179

7280
int FS::FileSeek(lfs_file_t* file_p, uint32_t pos) {
81+
Lock lock(*this);
7382
return lfs_file_seek(&lfs, file_p, pos, LFS_SEEK_SET);
7483
}
7584

7685
int FS::FileDelete(const char* fileName) {
86+
Lock lock(*this);
7787
return lfs_remove(&lfs, fileName);
7888
}
7989

8090
int FS::DirOpen(const char* path, lfs_dir_t* lfs_dir) {
91+
Lock lock(*this);
8192
return lfs_dir_open(&lfs, lfs_dir, path);
8293
}
8394

8495
int FS::DirClose(lfs_dir_t* lfs_dir) {
96+
Lock lock(*this);
8597
return lfs_dir_close(&lfs, lfs_dir);
8698
}
8799

88100
int FS::DirRead(lfs_dir_t* dir, lfs_info* info) {
101+
Lock lock(*this);
89102
return lfs_dir_read(&lfs, dir, info);
90103
}
91104

92105
int FS::DirRewind(lfs_dir_t* dir) {
106+
Lock lock(*this);
93107
return lfs_dir_rewind(&lfs, dir);
94108
}
95109

96110
int FS::DirCreate(const char* path) {
111+
Lock lock(*this);
97112
return lfs_mkdir(&lfs, path);
98113
}
99114

100115
int FS::Rename(const char* oldPath, const char* newPath) {
116+
Lock lock(*this);
101117
return lfs_rename(&lfs, oldPath, newPath);
102118
}
103119

104120
int FS::Stat(const char* path, lfs_info* info) {
121+
Lock lock(*this);
105122
return lfs_stat(&lfs, path, info);
106123
}
107124

108125
lfs_ssize_t FS::GetFSSize() {
126+
Lock lock(*this);
109127
return lfs_fs_size(&lfs);
110128
}
111129

src/components/fs/FS.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,40 @@
33
#include <cstdint>
44
#include "drivers/SpiNorFlash.h"
55
#include <littlefs/lfs.h>
6+
#include <FreeRTOS.h>
7+
#include <semphr.h>
68

79
namespace Pinetime {
810
namespace Controllers {
911
class FS {
1012
public:
1113
FS(Pinetime::Drivers::SpiNorFlash&);
1214

15+
// Serializes littlefs access across tasks (littlefs itself is not
16+
// thread-safe and the lfs_t state is shared). Every public FS method
17+
// takes it, so single calls need nothing; hold a Lock across a
18+
// multi-call sequence whose intermediate state must not be observed
19+
// torn - in particular any open-file handle that a concurrent Rename
20+
// or Delete of the same file would invalidate.
21+
class Lock {
22+
public:
23+
explicit Lock(FS& fs) : fs {fs} {
24+
xSemaphoreTakeRecursive(fs.mutex, portMAX_DELAY);
25+
}
26+
27+
~Lock() {
28+
xSemaphoreGiveRecursive(fs.mutex);
29+
}
30+
31+
Lock(const Lock&) = delete;
32+
Lock& operator=(const Lock&) = delete;
33+
Lock(Lock&&) = delete;
34+
Lock& operator=(Lock&&) = delete;
35+
36+
private:
37+
FS& fs;
38+
};
39+
1340
void Init();
1441

1542
int FileOpen(lfs_file_t* file_p, const char* fileName, const int flags);
@@ -41,6 +68,7 @@ namespace Pinetime {
4168

4269
private:
4370
Pinetime::Drivers::SpiNorFlash& flashDriver;
71+
SemaphoreHandle_t mutex = nullptr;
4472

4573
/*
4674
* External Flash MAP (4 MBytes)

0 commit comments

Comments
 (0)