Save persistently

Hello everyone,

a fellow student and I are trying to develop a firewall with an ESP32-wroom32 under RIOT. It should filter very simple UDP and TCP packets per IP, port and direction. Writing, deleting and changing rules will be done via a self-written API. Everything works great, but we can’t get the rules to be saved after a restart. The library provided by RIOT is causing problems. Has anyone had experience with saving in general? Btw the ESP32 doesn’t have an EEPROM. It does not have to be a solution from RIOT directly, it is also sufficient if the solution is so general that it can be used under RIOT and under different microcontrollers.

Many thanks in advance and best regards

Translated with DeepL.com (free version)

if there is no EEPROM/flash that you can use, how do you expect to save the rules?

The ESP32 series do have writeable flash, and I think I’ve used littlefs on it, but I couldn’t swear to that point at 6pm on a Friday.

Thanks for the comment. The ESP has a flash drive but no EEPROM. Litlefs2 was causing mounting issues, as were spiffs. That’s why I’m here. Also for future comments, please no theoretical approaches/solutions. Just actual solutions and possibly the method used to implement them. We may have overlooked something. Thanks in advance.

Welcome to the RIOT community.

Which library are you referring to? Can you share the errors you were getting?

1 Like

It never returns from “Formatting LittleFS…”. We’ve tried different slice sizes and buffer sizes. It looks like LittleFS’s format operation hangs on ESP32 when using RIOT OS’s mtd_mapper + periph_flash backend. We need a working persistent storage method for ESP32 in RIOT OS without ESP-IDF.

main.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "vfs.h"
#include "fs/littlefs2_fs.h"
#include "mtd.h"
#include "mtd_mapper.h"
#include "shell.h"

/* Filesystem placement */
#define MOUNT_POINT      "/fs"
#define LOG_FILE         "/fs/log.txt"
#define LFS_SIZE_BYTES   (1 * 256 * 1024)   

/* Some RIOT boards define MTD_0; fallback to device 0 if not */
#ifndef MTD_0
#define MTD_0 mtd_dev_get(0)
#endif

/* --- GLOBAL state: must persist after mount_lfs() returns --- */
static littlefs2_desc_t       g_lfs;
static mtd_mapper_parent_t    g_parent;
static mtd_mapper_region_t    g_region;
/* ------------------------------------------------------------ */

static int mount_lfs(void)
{
    mtd_dev_t *base = MTD_0;
    if (!base) { puts("ERROR: No base MTD (MTD_0)"); return -1; }

    const uint32_t sector_size   = base->page_size * base->pages_per_sector;
    if (!sector_size) { puts("ERROR: bad flash geometry"); return -1; }

    const uint32_t total_sectors = base->sector_count;
    const uint32_t lfs_sectors   = LFS_SIZE_BYTES / sector_size;
    if (lfs_sectors == 0 || lfs_sectors >= total_sectors) {
        puts("ERROR: LFS_SIZE_BYTES invalid for this flash");
        return -1;
    }

    const uint32_t start_sector = total_sectors - lfs_sectors;

    /* Initialize globals via temporaries to respect the macro semantics */
    mtd_mapper_parent_t tmp_parent = MTD_PARENT_INIT(base);
    mtd_mapper_region_t tmp_region = {
        .mtd = {
            .driver            = &mtd_mapper_driver,
            .sector_count      = lfs_sectors,
            .pages_per_sector  = base->pages_per_sector,
            .page_size         = base->page_size,
            .write_size        = base->write_size,
        },
        .parent = &g_parent,      /* will point to the global after copy */
        .sector = start_sector
    };

    g_parent = tmp_parent;
    g_region = tmp_region;

    memset(&g_lfs, 0, sizeof(g_lfs));
    g_lfs.dev = &g_region.mtd;

    /* Try to mount; if corrupt/blank, format then mount again */
    int res = lfs_mount(&g_lfs.fs, &g_lfs.config);
    if (res < 0) {
        puts("Formatting LittleFS...");
        if (lfs_format(&g_lfs.fs, &g_lfs.config) < 0) {
            puts("ERROR: lfs_format() failed");
            return -1;
        }
        if (lfs_mount(&g_lfs.fs, &g_lfs.config) < 0) {
            puts("ERROR: lfs_mount() failed after format");
            return -1;
        }
    }

    /* Register FS with VFS */
    vfs_mount_t mnt = {
        .mount_point  = MOUNT_POINT,
        .fs           = &littlefs2_file_system,
        .private_data = &g_lfs,
    };
    if (vfs_mount(&mnt) != 0) {
        puts("ERROR: vfs_mount() failed");
        lfs_unmount(&g_lfs.fs);
        return -1;
    }

    puts("LittleFS mounted at /fs");
    return 0;
}

/* -------- Shell commands -------- */

static int cmd_logshow(int argc, char **argv)
{
    (void)argc; (void)argv;
    FILE *fp = fopen(LOG_FILE, "r");
    if (!fp) { puts("No log file yet."); return 0; }
    puts("=== /fs/log.txt ===");
    char buf[128];
    while (fgets(buf, sizeof(buf), fp)) printf("%s", buf);
    puts("\n====================");
    fclose(fp);
    return 0;
}

static int cmd_logadd(int argc, char **argv)
{
    if (argc < 2) { puts("usage: logadd <text>"); return 1; }
    FILE *fp = fopen(LOG_FILE, "a");
    if (!fp) { puts("open for write failed"); return 1; }
    for (int i = 1; i < argc; i++) {
        fputs(argv[i], fp);
        if (i + 1 < argc) fputc(' ', fp);
    }
    fputc('\n', fp);
    fclose(fp);
    puts("OK");
    return 0;
}

static int cmd_logclear(int argc, char **argv)
{
    (void)argc; (void)argv;
    FILE *fp = fopen(LOG_FILE, "w");
    if (!fp) { puts("open for write failed"); return 1; }
    fclose(fp);
    puts("cleared");
    return 0;
}

static const shell_command_t cmds[] = {
    { "logshow",  "show /fs/log.txt",         cmd_logshow },
    { "logadd",   "append a line",            cmd_logadd  },
    { "logclear", "truncate the log file",    cmd_logclear},
    { NULL, NULL, NULL }
};

int main(void)
{
    puts("=== LittleFS2 test with shell (ESP32/RIOT) ===");

    if (mount_lfs() != 0) {
        puts("Mount failed — filesystem not available.");
    }

    puts("Commands: logshow | logadd <text> | logclear | ps | help");
    char line_buf[SHELL_DEFAULT_BUFSIZE];
    shell_run(cmds, line_buf, SHELL_DEFAULT_BUFSIZE);
    return 0;
}

Makefile:

include ../Makefile.net_common
RIOTBASE ?= $(CURDIR)/../../../..

# If no BOARD is found in the environment, use this default:
BOARD ?= esp32-wroom-32

QUIET ?= 1



USEMODULE += vfs
USEMODULE += littlefs2_fs
USEPKG    += littlefs2
USEMODULE += mtd
USEMODULE += mtd_mapper
USEMODULE += periph_flash

USEMODULE += shell
USEMODULE += ps
USEMODULE += vfs_default

USEMODULE += stdio_uart
USEMODULE += stdin 

# Tell RIOT where your partitions CSV is:
# (Path is relative to this Makefile; adjust if you put it somewhere else.)
CFLAGS += -DESP32_PARTITIONS_CSV=\"partitions.csv\"

include $(RIOTBASE)/Makefile.include