714 lines
22 KiB
C
714 lines
22 KiB
C
#include <zephyr/fs/littlefs.h>
|
|
#include <zephyr/fs/fs.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/pm/device.h>
|
|
#include <zephyr/sys/crc.h>
|
|
|
|
#include "fs_mgmt.h"
|
|
#include "buzz_proto.h"
|
|
|
|
LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
|
|
|
|
#define FS_PARTITION_ID FLASH_AREA_ID(littlefs_storage)
|
|
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);
|
|
|
|
#define QSPI_FLASH_NODE DT_ALIAS(qspi_flash)
|
|
static const struct device *flash_dev = DEVICE_DT_GET(QSPI_FLASH_NODE);
|
|
|
|
#define TAG_MAGIC "TAG!"
|
|
#define TAG_FORMAT_VERSION 1U
|
|
|
|
static struct fs_mount_t fs_storage_mnt = {
|
|
.type = FS_LITTLEFS,
|
|
.fs_data = &fs_storage_data,
|
|
.storage_dev = (void *)FS_PARTITION_ID,
|
|
.mnt_point = CONFIG_FS_MGMT_MOUNT_POINT,
|
|
};
|
|
|
|
static int open_count = 0;
|
|
static struct k_mutex flash_pm_lock;
|
|
|
|
#define ACK_WATERMARK (CONFIG_BUZZ_PROTO_SLAB_COUNT / 4)
|
|
|
|
typedef struct __attribute__((packed))
|
|
{
|
|
uint16_t total_size;
|
|
uint16_t version;
|
|
uint8_t magic[4];
|
|
} tag_footer_t;
|
|
|
|
K_MSGQ_DEFINE(fs_write_msgq, sizeof(struct fs_write_msg), CONFIG_BUZZ_PROTO_SLAB_COUNT, 4);
|
|
|
|
typedef enum {
|
|
FS_STATE_IDLE,
|
|
FS_STATE_RECEIVING_FILE,
|
|
FS_STATE_RECEIVING_TAGS,
|
|
FS_STATE_RECEIVING_FIRMWARE
|
|
} fs_thread_state_t;
|
|
|
|
static struct
|
|
{
|
|
fs_thread_state_t state;
|
|
struct fs_file_t file;
|
|
char filename[FS_MGMT_MAX_PATH_LENGTH];
|
|
uint32_t crc32;
|
|
uint16_t unacked_chunks;
|
|
off_t audio_len; // Offeset für Tags
|
|
} write_ctx;
|
|
|
|
/**
|
|
* @brief Puts the QSPI flash into deep sleep mode to save power
|
|
* Decrements the open count and suspends the flash if no more users are active
|
|
* @return 0 on success, negative error code on failure
|
|
*/
|
|
static int fs_mgmt_pm_flash_suspend(void)
|
|
{
|
|
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
|
if (!device_is_ready(flash_dev))
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
k_mutex_lock(&flash_pm_lock, K_FOREVER);
|
|
|
|
if (open_count > 0)
|
|
{
|
|
open_count--;
|
|
if (open_count == 0)
|
|
{
|
|
int rc = pm_device_action_run(flash_dev, PM_DEVICE_ACTION_SUSPEND);
|
|
if (rc < 0)
|
|
{
|
|
LOG_WRN("Could not suspend flash: %d", rc);
|
|
}
|
|
else
|
|
{
|
|
LOG_DBG("Flash entered deep power-down");
|
|
}
|
|
}
|
|
}
|
|
|
|
k_mutex_unlock(&flash_pm_lock);
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Resumes the QSPI flash from deep sleep mode
|
|
* Increments the open count and resumes the flash if it was previously suspended
|
|
* @return 0 on success, negative error code on failure
|
|
*/
|
|
static int fs_mgmt_pm_flash_resume(void)
|
|
{
|
|
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
|
if (!device_is_ready(flash_dev))
|
|
return -ENODEV;
|
|
|
|
k_mutex_lock(&flash_pm_lock, K_FOREVER);
|
|
|
|
if (open_count == 0)
|
|
{
|
|
int rc = pm_device_action_run(flash_dev, PM_DEVICE_ACTION_RESUME);
|
|
if (rc == 0)
|
|
{
|
|
k_busy_wait(50); // t-exit-dpd
|
|
LOG_DBG("Flash resumed");
|
|
}
|
|
}
|
|
open_count++;
|
|
|
|
k_mutex_unlock(&flash_pm_lock);
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
return 0;
|
|
}
|
|
|
|
int fs_mgmt_pm_open(struct fs_file_t *file, const char *path, fs_mode_t mode)
|
|
{
|
|
LOG_DBG("PM Opening file '%s' with mode 0x%02x", path, mode);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_open(file, path, mode);
|
|
if (rc < 0)
|
|
{
|
|
fs_mgmt_pm_flash_suspend();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_close(struct fs_file_t *file)
|
|
{
|
|
LOG_DBG("PM Closing file");
|
|
int rc = fs_close(file);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_opendir(struct fs_dir_t *dirp, const char *path)
|
|
{
|
|
LOG_DBG("PM Opening directory '%s'", path);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_opendir(dirp, path);
|
|
if (rc < 0)
|
|
{
|
|
fs_mgmt_pm_flash_suspend();
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_closedir(struct fs_dir_t *dirp)
|
|
{
|
|
LOG_DBG("PM Closing directory");
|
|
int rc = fs_closedir(dirp);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_unlink(const char *path)
|
|
{
|
|
LOG_DBG("PM Unlinking file '%s'", path);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_unlink(path);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_statvfs(const char *path, struct fs_statvfs *stat)
|
|
{
|
|
LOG_DBG("PM Getting filesystem stats for '%s'", path);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_statvfs(path, stat);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_stat(const char *path, struct fs_dirent *entry)
|
|
{
|
|
LOG_DBG("PM Getting stat for '%s'", path);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_stat(path, entry);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_mkdir(const char *path)
|
|
{
|
|
LOG_DBG("PM Creating directory '%s'", path);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_mkdir(path);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_rename(const char *old_path, const char *new_path)
|
|
{
|
|
LOG_DBG("PM Renaming '%s' to '%s'", old_path, new_path);
|
|
fs_mgmt_pm_flash_resume();
|
|
int rc = fs_rename(old_path, new_path);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_rm_recursive(char *path_buf, size_t max_len)
|
|
{
|
|
struct fs_dirent entry;
|
|
struct fs_dir_t dir;
|
|
int rc;
|
|
|
|
fs_mgmt_pm_flash_resume();
|
|
|
|
/* 1. Stat prüfen: Ist es eine Datei? */
|
|
rc = fs_stat(path_buf, &entry);
|
|
if (rc != 0)
|
|
{
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
/* Wenn es eine Datei ist, direkt löschen und beenden */
|
|
if (entry.type == FS_DIR_ENTRY_FILE)
|
|
{
|
|
rc = fs_unlink(path_buf);
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
/* 2. Es ist ein Verzeichnis. Schleife bis es leer ist. */
|
|
size_t orig_len = strlen(path_buf);
|
|
|
|
while (1)
|
|
{
|
|
fs_dir_t_init(&dir);
|
|
rc = fs_opendir(&dir, path_buf);
|
|
if (rc != 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
bool found_something = false;
|
|
|
|
/* Genau EINEN löschbaren Eintrag suchen */
|
|
while (1)
|
|
{
|
|
rc = fs_readdir(&dir, &entry);
|
|
if (rc != 0 || entry.name[0] == '\0')
|
|
{
|
|
break; /* Ende oder Fehler */
|
|
}
|
|
if (strcmp(entry.name, ".") == 0 || strcmp(entry.name, "..") == 0)
|
|
{
|
|
continue; /* Ignorieren */
|
|
}
|
|
|
|
found_something = true;
|
|
break; /* Treffer! Schleife abbrechen. */
|
|
}
|
|
|
|
/* WICHTIG: Das Verzeichnis SOFORT schließen, BEVOR wir rekurieren!
|
|
* Damit geben wir das File-Handle (NUM_DIRS) an Zephyr zurück. */
|
|
fs_closedir(&dir);
|
|
|
|
if (!found_something || rc != 0)
|
|
{
|
|
break; /* Verzeichnis ist nun restlos leer */
|
|
}
|
|
|
|
size_t name_len = strlen(entry.name);
|
|
if (orig_len + 1 + name_len >= max_len)
|
|
{
|
|
rc = -ENAMETOOLONG;
|
|
break;
|
|
}
|
|
|
|
/* Pfad für das gefundene Kindelement bauen */
|
|
path_buf[orig_len] = '/';
|
|
strcpy(&path_buf[orig_len + 1], entry.name);
|
|
|
|
/* Rekursiver Aufruf für das Kind */
|
|
rc = fs_mgmt_pm_rm_recursive(path_buf, max_len);
|
|
|
|
/* Puffer sofort wieder auf unser Verzeichnis zurückschneiden */
|
|
path_buf[orig_len] = '\0';
|
|
|
|
if (rc != 0)
|
|
{
|
|
break; /* Abbruch, falls beim Löschen des Kindes ein Fehler auftrat */
|
|
}
|
|
}
|
|
|
|
/* 3. Das nun restlos leere Verzeichnis selbst löschen */
|
|
if (rc == 0)
|
|
{
|
|
rc = fs_unlink(path_buf);
|
|
}
|
|
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_pm_mkdir_recursive(char *path)
|
|
{
|
|
int rc = 0;
|
|
struct fs_dirent entry;
|
|
char *p = path;
|
|
|
|
/* Führenden Slash überspringen, falls vorhanden (z. B. bei "/lfs") */
|
|
if (*p == '/')
|
|
{
|
|
p++;
|
|
}
|
|
|
|
/* Flash für den gesamten Durchlauf aktivieren */
|
|
fs_mgmt_pm_flash_resume();
|
|
|
|
while (*p != '\0')
|
|
{
|
|
if (*p == '/')
|
|
{
|
|
*p = '\0'; /* String temporär am aktuellen Slash terminieren */
|
|
|
|
/* Prüfen, ob dieser Pfadabschnitt bereits existiert */
|
|
rc = fs_stat(path, &entry);
|
|
|
|
if (rc == -ENOENT)
|
|
{
|
|
/* Existiert nicht -> anlegen */
|
|
rc = fs_mkdir(path);
|
|
if (rc != 0)
|
|
{
|
|
*p = '/'; /* Bei Fehler Slash wiederherstellen und abbrechen */
|
|
break;
|
|
}
|
|
}
|
|
else if (rc == 0)
|
|
{
|
|
/* Existiert -> prüfen, ob es ein Verzeichnis ist */
|
|
if (entry.type != FS_DIR_ENTRY_DIR)
|
|
{
|
|
rc = -ENOTDIR;
|
|
*p = '/';
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Anderer Dateisystemfehler */
|
|
*p = '/';
|
|
break;
|
|
}
|
|
|
|
*p = '/'; /* Slash für den nächsten Schleifendurchlauf wiederherstellen */
|
|
}
|
|
p++;
|
|
}
|
|
|
|
/* Letztes Element verarbeiten, falls der Pfad nicht mit '/' endet */
|
|
if (rc == 0 && p > path && *(p - 1) != '/')
|
|
{
|
|
rc = fs_stat(path, &entry);
|
|
if (rc == -ENOENT)
|
|
{
|
|
rc = fs_mkdir(path);
|
|
}
|
|
else if (rc == 0)
|
|
{
|
|
if (entry.type != FS_DIR_ENTRY_DIR)
|
|
{
|
|
rc = -ENOTDIR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Flash am Ende wieder in den Suspend schicken */
|
|
fs_mgmt_pm_flash_suspend();
|
|
return rc;
|
|
}
|
|
|
|
int fs_mgmt_init(void)
|
|
{
|
|
k_mutex_init(&flash_pm_lock);
|
|
|
|
if (!device_is_ready(flash_dev))
|
|
{
|
|
LOG_ERR("Flash device not ready!");
|
|
return -ENODEV;
|
|
}
|
|
|
|
fs_mgmt_pm_flash_resume();
|
|
|
|
int rc = fs_mount(&fs_storage_mnt);
|
|
|
|
if (rc < 0)
|
|
{
|
|
LOG_ERR("Error mounting filesystem: %d", rc);
|
|
return rc;
|
|
}
|
|
fs_mgmt_pm_flash_suspend();
|
|
LOG_DBG("Filesystem mounted successfully");
|
|
return 0;
|
|
}
|
|
|
|
static int fs_get_tag_bounds(struct fs_file_t *fp, off_t file_size,
|
|
size_t *audio_limit, size_t *payload_len, bool *has_tag)
|
|
{
|
|
tag_footer_t footer;
|
|
|
|
if (audio_limit == NULL || payload_len == NULL || has_tag == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
*has_tag = false;
|
|
*audio_limit = (size_t)file_size;
|
|
*payload_len = 0U;
|
|
|
|
if (file_size < (off_t)sizeof(tag_footer_t))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
fs_seek(fp, -(off_t)sizeof(tag_footer_t), FS_SEEK_END);
|
|
if (fs_read(fp, &footer, sizeof(tag_footer_t)) != sizeof(tag_footer_t))
|
|
{
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return -EIO;
|
|
}
|
|
|
|
if (memcmp(footer.magic, TAG_MAGIC, 4) != 0)
|
|
{
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return 0;
|
|
}
|
|
|
|
uint16_t tag_version = sys_le16_to_cpu(footer.version);
|
|
uint16_t tag_len = sys_le16_to_cpu(footer.total_size);
|
|
|
|
if (tag_version != TAG_FORMAT_VERSION)
|
|
{
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (tag_len > (uint16_t)file_size || tag_len < sizeof(tag_footer_t))
|
|
{
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
*has_tag = true;
|
|
*audio_limit = (size_t)file_size - tag_len;
|
|
*payload_len = tag_len - sizeof(tag_footer_t);
|
|
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return 0;
|
|
}
|
|
|
|
ssize_t fs_get_audio_data_len(struct fs_file_t *fp)
|
|
{
|
|
off_t file_size;
|
|
size_t audio_limit = 0U;
|
|
size_t payload_len = 0U;
|
|
bool has_tag = false;
|
|
|
|
fs_seek(fp, 0, FS_SEEK_END);
|
|
file_size = fs_tell(fp);
|
|
|
|
if (file_size < 0)
|
|
{
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return -EIO;
|
|
}
|
|
|
|
if (fs_get_tag_bounds(fp, file_size, &audio_limit, &payload_len, &has_tag) < 0)
|
|
{
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return -EIO;
|
|
}
|
|
|
|
fs_seek(fp, 0, FS_SEEK_SET);
|
|
return has_tag ? (ssize_t)audio_limit : file_size;
|
|
}
|
|
|
|
int fs_mgmt_submit_write(struct fs_write_msg *msg)
|
|
{
|
|
return k_msgq_put(&fs_write_msgq, msg, K_NO_WAIT);
|
|
}
|
|
|
|
static void fs_thread_entry(void *p1, void *p2, void *p3)
|
|
{
|
|
LOG_INF("FS Write Thread started");
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
fs_file_t_init(&write_ctx.file);
|
|
struct fs_write_msg msg;
|
|
|
|
while (1)
|
|
{
|
|
/* Watchdog nur bei aktiven Transfers */
|
|
k_timeout_t wait_time = (write_ctx.state == FS_STATE_IDLE) ? K_FOREVER : K_SECONDS(2);
|
|
int rc = k_msgq_get(&fs_write_msgq, &msg, wait_time);
|
|
|
|
if (rc == -EAGAIN)
|
|
{
|
|
LOG_WRN("Write timeout! Aborting transfer.");
|
|
if (write_ctx.state == FS_STATE_RECEIVING_FILE) {
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
fs_mgmt_pm_unlink(write_ctx.filename);
|
|
}
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
continue;
|
|
}
|
|
|
|
switch (write_ctx.state)
|
|
{
|
|
case FS_STATE_IDLE:
|
|
if (msg.op == FS_WRITE_OP_FILE_START)
|
|
{
|
|
if (msg.data_len >= sizeof(write_ctx.filename)) {
|
|
LOG_ERR("Filename too long");
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, ENAMETOOLONG, msg.slab_ptr);
|
|
break;
|
|
}
|
|
|
|
memcpy(write_ctx.filename, msg.slab_ptr + msg.data_offset, msg.data_len);
|
|
write_ctx.filename[msg.data_len] = '\0';
|
|
|
|
fs_mgmt_pm_unlink(write_ctx.filename);
|
|
rc = fs_mgmt_pm_open(&write_ctx.file, write_ctx.filename, FS_O_CREATE | FS_O_WRITE);
|
|
|
|
if (rc == 0) {
|
|
write_ctx.state = FS_STATE_RECEIVING_FILE;
|
|
write_ctx.crc32 = 0;
|
|
write_ctx.unacked_chunks = 0;
|
|
LOG_INF("File transfer started: %s (Expected: %u bytes)", write_ctx.filename, msg.metadata);
|
|
|
|
uint16_t credits = buzz_proto_get_free_rx_slabs();
|
|
buzz_proto_buf_free(&msg.slab_ptr);
|
|
buzz_proto_send_ack(msg.reply_cb, credits);
|
|
} else {
|
|
LOG_ERR("Failed to open %s: %d", write_ctx.filename, rc);
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr);
|
|
}
|
|
}/* Innerhalb von case FS_STATE_IDLE: */
|
|
else if (msg.op == FS_WRITE_OP_TAGS_START)
|
|
{
|
|
if (msg.data_len >= sizeof(write_ctx.filename)) {
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, ENAMETOOLONG, msg.slab_ptr);
|
|
break;
|
|
}
|
|
|
|
memcpy(write_ctx.filename, msg.slab_ptr + msg.data_offset, msg.data_len);
|
|
write_ctx.filename[msg.data_len] = '\0';
|
|
|
|
/* Datei öffnen: Nur Lese- und Schreibrechte, Datei muss bereits existieren */
|
|
int rc = fs_mgmt_pm_open(&write_ctx.file, write_ctx.filename, FS_O_READ | FS_O_WRITE);
|
|
|
|
if (rc == 0) {
|
|
ssize_t audio_len = fs_get_audio_data_len(&write_ctx.file);
|
|
|
|
if (audio_len < 0) {
|
|
LOG_ERR("Failed to get audio length: %d", (int)audio_len);
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, EIO, msg.slab_ptr);
|
|
break;
|
|
}
|
|
|
|
/* Datei ab dem Ende der Audiodaten abschneiden (alte Tags entfernen) */
|
|
rc = fs_truncate(&write_ctx.file, audio_len);
|
|
if (rc != 0) {
|
|
LOG_ERR("Failed to truncate file: %d", rc);
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr);
|
|
break;
|
|
}
|
|
|
|
/* File-Pointer exakt an das neue Ende (audio_len) setzen */
|
|
fs_seek(&write_ctx.file, audio_len, FS_SEEK_SET);
|
|
|
|
write_ctx.state = FS_STATE_RECEIVING_TAGS;
|
|
write_ctx.crc32 = 0;
|
|
write_ctx.unacked_chunks = 0;
|
|
write_ctx.audio_len = audio_len;
|
|
|
|
LOG_INF("Tags transfer started: %s (Expected tags: %u bytes)", write_ctx.filename, msg.metadata);
|
|
|
|
uint16_t credits = buzz_proto_get_free_rx_slabs();
|
|
buzz_proto_buf_free(&msg.slab_ptr);
|
|
buzz_proto_send_ack(msg.reply_cb, credits);
|
|
} else {
|
|
LOG_ERR("Failed to open %s for tags: %d", write_ctx.filename, rc);
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr);
|
|
}
|
|
} else if ( msg.op == FS_WRITE_OP_FW_START)
|
|
{
|
|
LOG_WRN("Operation not yet fully implemented in FS state machine");
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, ENOSYS, msg.slab_ptr);
|
|
}
|
|
break;
|
|
|
|
case FS_STATE_RECEIVING_FILE:
|
|
if (msg.op == FS_WRITE_OP_FILE_CHUNK && msg.slab_ptr)
|
|
{
|
|
ssize_t written = fs_write(&write_ctx.file, msg.slab_ptr + msg.data_offset, msg.data_len);
|
|
|
|
if (written == msg.data_len) {
|
|
write_ctx.crc32 = crc32_ieee_update(write_ctx.crc32, msg.slab_ptr + msg.data_offset, msg.data_len);
|
|
buzz_proto_buf_free(&msg.slab_ptr);
|
|
write_ctx.unacked_chunks++;
|
|
if (write_ctx.unacked_chunks >= ACK_WATERMARK) {
|
|
uint16_t free_slabs = buzz_proto_get_free_rx_slabs();
|
|
uint16_t credits_to_send = MIN(free_slabs, write_ctx.unacked_chunks);
|
|
if (credits_to_send > 0) {
|
|
buzz_proto_send_ack(msg.reply_cb, credits_to_send);
|
|
write_ctx.unacked_chunks -= credits_to_send;
|
|
}
|
|
}
|
|
} else {
|
|
LOG_ERR("Flash write failed!");
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, EIO, msg.slab_ptr);
|
|
}
|
|
}
|
|
else if (msg.op == FS_WRITE_OP_FILE_END)
|
|
{
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
|
|
if (write_ctx.crc32 == msg.metadata) {
|
|
LOG_INF("File transfer finished. CRC valid: 0x%08X", write_ctx.crc32);
|
|
buzz_proto_send_success_reusing_slab(msg.reply_cb, BUZZ_DATA_FILE_PUT, msg.slab_ptr);
|
|
} else {
|
|
LOG_ERR("CRC Mismatch! Expected: 0x%08X, Got: 0x%08X", msg.metadata, write_ctx.crc32);
|
|
fs_mgmt_pm_unlink(write_ctx.filename);
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, EBADMSG, msg.slab_ptr);
|
|
}
|
|
}
|
|
else if (msg.op == FS_WRITE_OP_ABORT)
|
|
{
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
fs_mgmt_pm_unlink(write_ctx.filename);
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
if (msg.slab_ptr) buzz_proto_buf_free(&msg.slab_ptr);
|
|
}
|
|
break;
|
|
|
|
case FS_STATE_RECEIVING_TAGS:
|
|
if (msg.op == FS_WRITE_OP_FILE_CHUNK && msg.slab_ptr)
|
|
{
|
|
ssize_t written = fs_write(&write_ctx.file, msg.slab_ptr + msg.data_offset, msg.data_len);
|
|
|
|
if (written == msg.data_len) {
|
|
write_ctx.crc32 = crc32_ieee_update(write_ctx.crc32, msg.slab_ptr + msg.data_offset, msg.data_len);
|
|
buzz_proto_buf_free(&msg.slab_ptr);
|
|
|
|
write_ctx.unacked_chunks++;
|
|
if (write_ctx.unacked_chunks >= ACK_WATERMARK) {
|
|
uint16_t free_slabs = buzz_proto_get_free_rx_slabs();
|
|
uint16_t credits_to_send = MIN(free_slabs, write_ctx.unacked_chunks);
|
|
if (credits_to_send > 0) {
|
|
buzz_proto_send_ack(msg.reply_cb, credits_to_send);
|
|
write_ctx.unacked_chunks -= credits_to_send;
|
|
}
|
|
}
|
|
} else {
|
|
LOG_ERR("Flash write failed during tags transfer!");
|
|
fs_truncate(&write_ctx.file, write_ctx.audio_len); /* Rollback */
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, EIO, msg.slab_ptr);
|
|
}
|
|
}
|
|
else if (msg.op == FS_WRITE_OP_FILE_END)
|
|
{
|
|
if (write_ctx.crc32 == msg.metadata) {
|
|
LOG_INF("Tags transfer finished. CRC valid: 0x%08X", write_ctx.crc32);
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
buzz_proto_send_success_reusing_slab(msg.reply_cb, BUZZ_DATA_TAGS_PUT, msg.slab_ptr);
|
|
} else {
|
|
LOG_ERR("Tags CRC Mismatch! Expected: 0x%08X, Got: 0x%08X", msg.metadata, write_ctx.crc32);
|
|
fs_truncate(&write_ctx.file, write_ctx.audio_len); /* Rollback */
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, EBADMSG, msg.slab_ptr);
|
|
}
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
}
|
|
else if (msg.op == FS_WRITE_OP_ABORT)
|
|
{
|
|
fs_truncate(&write_ctx.file, write_ctx.audio_len); /* Rollback */
|
|
fs_mgmt_pm_close(&write_ctx.file);
|
|
write_ctx.state = FS_STATE_IDLE;
|
|
if (msg.slab_ptr) buzz_proto_buf_free(&msg.slab_ptr);
|
|
}
|
|
break;
|
|
|
|
case FS_STATE_RECEIVING_FIRMWARE:
|
|
break;
|
|
}
|
|
|
|
/* Garbage Collection: Ungültige Operationen im falschen State abfangen */
|
|
if (write_ctx.state == FS_STATE_IDLE && msg.op == FS_WRITE_OP_FILE_CHUNK && msg.slab_ptr) {
|
|
buzz_proto_buf_free(&msg.slab_ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
K_THREAD_DEFINE(fs_thread, CONFIG_FS_MGMT_THREAD_STACK_SIZE, fs_thread_entry,
|
|
NULL, NULL, NULL, CONFIG_FS_MGMT_THREAD_PRIORITY, 0, 0); |