Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
LittleFsStorageManager.cpp
Go to the documentation of this file.
3#include <hardware/flash.h> // for flash_range_program, flash_range_erase
4#include <hardware/sync.h> // for save_and_disable_interrupts, restore_interrupts
5#include <pico/multicore.h> // for multicore_lockout_start_blocking / end_blocking
6#include <hardware/regs/addressmap.h> // for XIP_BASE if not already defined
7#include <cstring>
8#include <iostream>
9#include <FreeRTOS.h> // for FreeRTOS types and functions
10#include <semphr.h> // for SemaphoreHandle_t, StaticSemaphore_t, xSemaphoreCreateMutexStatic, xSemaphoreTake, xSemaphoreGive
11#include <pico/flash.h> // for flash_safe_execute
12#include "utility/utility.h" // for runtimeStats
13#include "framework_config.h"
14#include "DebugTrace.h"
16
17extern "C"
18{
19 extern uint8_t __flash_lfs_start;
20 extern uint8_t __flash_lfs_end;
21}
22
23// Static mutex for LittleFS thread safety
25SemaphoreHandle_t LittleFsStorageManager::lfs_mutex = xSemaphoreCreateMutexStatic(&lfs_mutex_buf);
26
31
32int LittleFsStorageManager::lfs_lock(const struct lfs_config *c) {
33 assert(lfs_mutex != nullptr);
34 return (xSemaphoreTake(lfs_mutex, portMAX_DELAY) == pdTRUE) ? 0 : -1;
35}
36
37int LittleFsStorageManager::lfs_unlock(const struct lfs_config *c) {
38 return (xSemaphoreGive(lfs_mutex) == pdTRUE) ? 0 : -1;
39}
40
41int LittleFsStorageManager::lfs_read_cb(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
42 void *buffer, lfs_size_t size)
43{
44 auto *self = static_cast<LittleFsStorageManager *>(c->context);
45 uintptr_t addr = self->flashBase + block * c->block_size + off;
46 std::memcpy(buffer, reinterpret_cast<const void *>(addr), size);
47 return 0;
48}
49
50// Public program callback that gets registered in config.erase
51int LittleFsStorageManager::lfs_prog_cb(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
52 const void *buffer, lfs_size_t size)
53{
54
55#if (configNUM_CORES > 1)
56 return lfs_prog_cb_multicore(c, block, off, buffer, size);
57#else
58 return lfs_prog_cb_singlecore(c, block, off, buffer, size);
59#endif
60}
61
62int LittleFsStorageManager::lfs_prog_cb_singlecore(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
63 const void *buffer, lfs_size_t size)
64{
65 auto *self = static_cast<LittleFsStorageManager *>(c->context);
66 uintptr_t addr = self->flashBase + block * c->block_size + off;
67 uint32_t ints = save_and_disable_interrupts();
68 flash_range_program(addr - XIP_BASE, reinterpret_cast<const uint8_t *>(buffer), size);
69 restore_interrupts(ints);
70 return 0;
71}
72
74{
75 uint32_t addr;
76 const uint8_t *data;
77 size_t size;
78};
79
80static void __not_in_flash_func(flash_prog_callback)(void *p)
81{
82 auto *params = static_cast<FlashProgParams *>(p);
83 flash_range_program(params->addr, params->data, params->size);
84}
85
86static int __not_in_flash_func(lfs_prog_multicore)(const struct lfs_config *c,
87 lfs_block_t block,
88 lfs_off_t off,
89 const void *buffer,
90 lfs_size_t size)
91{
92 auto *self = static_cast<LittleFsStorageManager *>(c->context);
93 uintptr_t addr = self->getFlashBase() + block * c->block_size + off;
94
95 FlashProgParams params = {
96 .addr = addr - XIP_BASE,
97 .data = static_cast<const uint8_t *>(buffer),
98 .size = size};
99 int result = flash_safe_execute(flash_prog_callback, &params, 1000);
100 return (result == PICO_OK) ? 0 : -1;
101}
102
103int LittleFsStorageManager::lfs_prog_cb_multicore(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
104{
105 auto *self = static_cast<LittleFsStorageManager *>(c->context);
106 uintptr_t addr = self->flashBase + block * c->block_size + off;
107 return lfs_prog_multicore(c, block, off, buffer, size);
108}
109
110int LittleFsStorageManager::lfs_erase_cb(const struct lfs_config *c, lfs_block_t block)
111{
112#if (configNUM_CORES > 1)
113 return lfs_erase_cb_multicore(c, block);
114#else
115 return lfs_erase_cb_singlecore(c, block);
116#endif
117}
118
119int LittleFsStorageManager::lfs_erase_cb_singlecore(const struct lfs_config *c, lfs_block_t block)
120{
121 auto *self = static_cast<LittleFsStorageManager *>(c->context);
122 uintptr_t addr = self->flashBase + block * c->block_size;
123 uint32_t ints = save_and_disable_interrupts();
124 flash_range_erase(addr - XIP_BASE, c->block_size);
125 restore_interrupts(ints);
126 return 0;
127}
128
130{
131 uint32_t addr;
132 size_t size;
133};
134
135// RAM function that does the actual erase
136static void __not_in_flash_func(flash_erase_callback)(void *p)
137{
138 auto *params = static_cast<FlashEraseParams *>(p);
139 flash_range_erase(params->addr, params->size);
140}
141
142static int lfs_erase_cb_flashsafe(const struct lfs_config *c, lfs_block_t block)
143{
144 auto *self = static_cast<LittleFsStorageManager *>(c->context);
145 uintptr_t addr = self->getFlashBase() + block * c->block_size;
146
147 FlashEraseParams eraseParams = {
148 .addr = addr - XIP_BASE,
149 .size = c->block_size};
150 int result = flash_safe_execute(flash_erase_callback, &eraseParams, 1000);
151 return (result == PICO_OK) ? 0 : -1;
152}
153
154// Public erase callback that gets registered in config.erase
155int LittleFsStorageManager::lfs_erase_cb_multicore(const struct lfs_config *c, lfs_block_t block)
156{
157 auto *self = static_cast<LittleFsStorageManager *>(c->context);
158 uintptr_t addr = static_cast<LittleFsStorageManager *>(c->context)->flashBase + block * c->block_size;
159 int err = lfs_erase_cb_flashsafe(c, block);
160 return err;
161}
162
163extern "C"
164{
165 extern uint8_t __flash_lfs_start;
166 extern uint8_t __flash_lfs_end;
167}
168
170{
171 flashBase = reinterpret_cast<uintptr_t>(&__flash_lfs_start);
172 flashSize = reinterpret_cast<uintptr_t>(&__flash_lfs_end) - flashBase;
173
174 std::memset(&config, 0, sizeof(config));
175
176 config.context = this;
177 config.read = lfs_read_cb;
178 config.prog = lfs_prog_cb;
179 config.erase = lfs_erase_cb;
180 config.sync = [](const struct lfs_config *) -> int
181 { return 0; };
182
183 config.read_size = 256;
184 config.prog_size = 256;
185 config.block_size = 4096;
186 config.block_count = flashSize / config.block_size;
187 config.cache_size = 256;
188 config.lookahead_size = 256;
189 config.block_cycles = 500;
190 config.compact_thresh = (lfs_size_t)-1;
191#if defined(LFS_THREADSAFE)
192 config.lock = lfs_lock;
193 config.unlock = lfs_unlock;
194#endif
195 // printf("[LittleFS] Configured, flash base: 0x%08x, size: %zu bytes (%zu blocks)\n",
196 // static_cast<unsigned>(flashBase), flashSize, config.block_count);
197}
198
200{
201 if( mounted)
202 {
203 TRACE("[LittleFs] Already mounted\n");
204 return true;
205 }
206 TRACE("[LittleFs] Mounting LittleFS...\n");
207 int err = lfs_mount(&lfs, &config);
208 if (err < 0)
209 {
210 printf("[LittleFs] Mount failed with error %d\n", err);
211 return false;
212 }
213 TRACE("[LittleFs] Mounted successfully\n");
214 return mounted = true;
215}
216
218{
219 if (mounted)
220 {
221 lfs_unmount(&lfs);
222 mounted = false;
223 }
224 return true;
225}
226
228{
229 return mounted;
230}
231
232bool LittleFsStorageManager::exists(const std::string &path)
233{
234 struct lfs_info info;
235 int err = lfs_stat(&lfs, path.c_str(), &info);
236 return (err == 0);
237}
238
239bool LittleFsStorageManager::remove(const std::string &path)
240{
241 return lfs_remove(&lfs, path.c_str()) == 0;
242}
243
244bool LittleFsStorageManager::rename(const std::string &from, const std::string &to)
245{
246 return lfs_rename(&lfs, from.c_str(), to.c_str()) == 0;
247}
248
249bool LittleFsStorageManager::readFile(const std::string &path, std::vector<uint8_t> &out)
250{
251 lfs_file_t file;
252 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_RDONLY) < 0)
253 return false;
254
255 lfs_soff_t size = lfs_file_size(&lfs, &file);
256 if (size < 0)
257 {
258 lfs_file_close(&lfs, &file);
259 return false;
260 }
261
262 out.resize(size);
263 int bytes = lfs_file_read(&lfs, &file, out.data(), size);
264 lfs_file_close(&lfs, &file);
265 return (bytes >= 0) && (bytes == size);
266}
267
268bool LittleFsStorageManager::readFileString(const std::string &path, uint32_t startPosition, uint32_t length, std::string &buffer){
269 lfs_file_t file;
270 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_RDONLY) < 0)
271 return false;
272
273 lfs_soff_t size = lfs_file_size(&lfs, &file);
274 if (size < 0 || startPosition >= size)
275 {
276 lfs_file_close(&lfs, &file);
277 return false;
278 }
279
280 if (startPosition + length > size)
281 length = size - startPosition;
282
283 buffer.resize(length);
284 lfs_file_seek(&lfs, &file, startPosition, LFS_SEEK_SET);
285 int bytesRead = lfs_file_read(&lfs, &file, buffer.data(), length);
286 lfs_file_close(&lfs, &file);
287 return (bytesRead >= 0) && (bytesRead == static_cast<int>(length));
288}
289
290bool LittleFsStorageManager::writeFile(const std::string &path, const std::vector<uint8_t> &data)
291{
292 lfs_file_t file;
293 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) < 0)
294 return false;
295 int written = lfs_file_write(&lfs, &file, data.data(), data.size());
296 int closed = lfs_file_close(&lfs, &file);
297 return (written == static_cast<int>(data.size())) && (closed == 0);
298}
299
300bool LittleFsStorageManager::writeFile(const std::string& path, const unsigned char* data, size_t size)
301{
302 if (!mounted)
303 return false;
304
305 lfs_file_t file;
306 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) < 0)
307 return false;
308
309 int written = lfs_file_write(&lfs, &file, data, size);
310 lfs_file_close(&lfs, &file);
311
312 return (written == static_cast<int>(size));
313}
314
315bool LittleFsStorageManager::appendToFile(const std::string &path, const uint8_t *data, size_t size)
316{
317 lfs_file_t file;
318 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) < 0)
319 {
320 printf("[LittleFS] appendToFile: open failed for '%s'\n", path.c_str());
321 lfs_file_close(&lfs, &file);
322 return false;
323 }
324 int written = lfs_file_write(&lfs, &file, data, size);
325 if (written < 0)
326 {
327 printf("[LittleFS] appendToFile: write failed for '%s'\n", path.c_str());
328 lfs_file_close(&lfs, &file);
329 return false;
330 }
331 lfs_file_close(&lfs, &file);
332 return written == (int)size;
333}
334
335bool LittleFsStorageManager::streamFile(const std::string &path, std::function<void(const uint8_t *, size_t)> chunkCallback)
336{
337 lfs_file_t file;
338 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_RDONLY) < 0)
339 return false;
340
341 uint8_t buf[HTTP_BUFFER_SIZE];
342 int readBytes;
343 bool success = true;
344
345 while ((readBytes = lfs_file_read(&lfs, &file, buf, sizeof(buf))) > 0)
346 {
347 chunkCallback(buf, readBytes);
348 }
349
350 if (readBytes < 0)
351 {
352 printf("[LittleFS] streamFile: read failed for '%s'\n", path.c_str());
353 success = false;
354 }
355
356 lfs_file_close(&lfs, &file);
357 return success;
358}
359
360size_t LittleFsStorageManager::getFileSize(const std::string &path)
361{
362 lfs_file_t file;
363 if (lfs_file_open(&lfs, &file, path.c_str(), LFS_O_RDONLY) < 0)
364 {
365 printf("[LittleFS] getFileSize: open failed for '%s'\n", path.c_str());
366 return 0;
367 }
368 lfs_soff_t size = lfs_file_size(&lfs, &file);
369 lfs_file_close(&lfs, &file);
370 return size < 0 ? 0 : size;
371}
372
373bool LittleFsStorageManager::listDirectory(const std::string &path, std::vector<FileInfo> &out)
374{
375 if(!mounted)
376 {
377 mount();
378 }
379 lfs_dir_t dir;
380 struct lfs_info info = {};
381
382 if (lfs_dir_open(&lfs, &dir, path.c_str()) < 0)
383 return false;
384
385 while (lfs_dir_read(&lfs, &dir, &info) > 0)
386 {
387 if (strcmp(info.name, ".") == 0 || strcmp(info.name, "..") == 0)
388 continue;
389
390 FileInfo entry;
391 entry.name = info.name;
392 entry.size = info.size;
393 entry.isDirectory = (info.type == LFS_TYPE_DIR);
394 entry.isReadOnly = false; // LittleFS doesn't expose this, so hardcoded
395 out.push_back(entry);
396 }
397
398 lfs_dir_close(&lfs, &dir);
399 return true;
400}
401
402
403bool LittleFsStorageManager::createDirectory(const std::string &path)
404{
405 TRACE("[LittleFS] Creating directory '%s'\n", path.c_str());
406 if (lfs_mkdir(&lfs, path.c_str()) < 0)
407 {
408 printf("[LittleFS] Failed to create directory '%s'\n", path.c_str());
409 return false;
410 }
411 TRACE("[LittleFS] Directory '%s' created successfully\n", path.c_str());
412 return true;
413}
414
415bool LittleFsStorageManager::removeDirectory(const std::string &path)
416{
417 return lfs_remove(&lfs, path.c_str()) == 0;
418}
419
421{
422 // Note that this function will unmount the filesystem before formatting
423 // and remount it afterwards if successful.
424
425
426
427 bool result = false;
428
429 if(mounted)
430 {
431 TRACE("[LittleFs] Unmounting before format\n");
432 unmount();
433 }
434 int err = lfs_format(&lfs, &config);
435 result = (err == 0);
436
437 if (result)
438 {
439 printf("[LittleFs] Format successful\n");
440 mount();
441 }
442 else
443 {
444 printf("[LittleFs] Format failed\n");
445 }
446 return result;
447}
448
449std::unique_ptr<StorageFileReader> LittleFsStorageManager::openReader(const std::string& path) {
450 if (!mount()) return nullptr;
451 auto reader = std::make_unique<LittleFsFileReader>(&lfs);
452 if (!reader) {
453 return nullptr;
454 }
455 if (!reader->open(path)) return nullptr;
456 return reader;
457}
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
Definition DebugTrace.h:170
#define TRACE(...)
Default trace (INFO level).
Definition DebugTrace.h:187
uint8_t __flash_lfs_start
static int __not_in_flash_func() lfs_prog_multicore(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
static int lfs_erase_cb_flashsafe(const struct lfs_config *c, lfs_block_t block)
uint8_t __flash_lfs_end
static void __not_in_flash_func() flash_prog_callback(void *p)
static void __not_in_flash_func() flash_erase_callback(void *p)
Flash-backed implementation of the StorageManager interface using LittleFS.
A LittleFS-based implementation of StorageManager, storing files in flash memory.
bool unmount() override
Unmount the LittleFS filesystem.
bool rename(const std::string &from, const std::string &to) override
Rename a file or directory.
bool formatStorage() override
Format the filesystem.
bool appendToFile(const std::string &path, const uint8_t *data, size_t size) override
Append data to a file.
static int lfs_erase_cb_multicore(const struct lfs_config *c, lfs_block_t block)
static int lfs_lock(const struct lfs_config *c)
bool writeFile(const std::string &path, const std::vector< uint8_t > &data) override
Write a byte vector to a file (overwrite).
uintptr_t getFlashBase() const
get Flash base address
std::unique_ptr< StorageFileReader > openReader(const std::string &path) override
open a file for streaming read line access.
static int lfs_prog_cb_multicore(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
static StaticSemaphore_t lfs_mutex_buf
static int lfs_unlock(const struct lfs_config *c)
bool streamFile(const std::string &path, std::function< void(const uint8_t *, size_t)> chunkCallback) override
Stream a file in chunks using a callback.
bool createDirectory(const std::string &path) override
Create a new directory.
bool removeDirectory(const std::string &path) override
Remove a directory.
bool remove(const std::string &path) override
Remove a file or directory.
bool isMounted() const override
Check if the filesystem is mounted.
static int lfs_prog_cb(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
LittleFsStorageManager()
Construct the manager and configure the filesystem.
bool readFileString(const std::string &path, uint32_t startPosition, uint32_t length, std::string &buffer)
Read a file string into a memory buffer.
bool mount() override
Mount the LittleFS filesystem.
static int lfs_read_cb(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
bool exists(const std::string &path) override
Check if a file or directory exists.
static int lfs_erase_cb_singlecore(const struct lfs_config *c, lfs_block_t block)
static int lfs_prog_cb_singlecore(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
bool listDirectory(const std::string &path, std::vector< FileInfo > &out) override
List files in a directory.
size_t getFileSize(const std::string &path) override
Get the size of a file.
bool readFile(const std::string &path, std::vector< uint8_t > &out) override
Read a file into a byte vector.
static SemaphoreHandle_t lfs_mutex
static int lfs_erase_cb(const struct lfs_config *c, lfs_block_t block)
Delegates to user or system configuration.
#define HTTP_BUFFER_SIZE
Size of the HTTP buffer for request/response data.
Structure representing metadata for a file or directory.
bool isDirectory
True if item is a directory.
size_t size
Size in bytes.
bool isReadOnly
True if item is read-only.
std::string name
File or directory name.
System utilities for diagnostics, memory, stack usage, and tracing.