File upload. Yeah

This commit is contained in:
2026-03-17 15:02:34 +01:00
parent 6ec66cd9da
commit 574ab9fa30
19 changed files with 1479 additions and 250 deletions

View File

@@ -16,7 +16,31 @@ if FS_MGMT
default "/lfs"
help
Set the mount point for the Littlefs file system. Default is "/lfs".
config FS_MGMT_AUDIO_SUBDIR
string "Audio File Path"
default "/a"
help
Set the path for the audio file within the file system. Default is "/a".
config FS_MGMT_SYSTEM_SUBDIR
string "System File Path"
default "/sys"
help
Set the path for the system file within the file system. Default is "/sys".
config FS_MGMT_THREAD_STACK_SIZE
int "File System Management Thread Stack Size"
default 2048
help
Set the stack size for the file system management thread. Default is 2048 bytes.
config FS_MGMT_THREAD_PRIORITY
int "File System Management Thread Priority"
default 6
help
Set the priority for the file system management thread. Default is 6.
if SOC_SERIES_NRF52X
config PM_PARTITION_REGION_LITTLEFS_EXTERNAL
default y

View File

@@ -2,25 +2,41 @@
#define FS_MGMT_H
#include <zephyr/fs/fs.h>
#include "buzz_proto.h"
#define FS_MGMT_MAX_PATH_LENGTH 32
#define FS_AUDIO_PATH CONFIG_FS_MGMT_MOUNT_POINT "/a"
#define FS_SYSTEM_PATH CONFIG_FS_MGMT_MOUNT_POINT "/sys"
#define FS_AUDIO_PATH CONFIG_FS_MGMT_MOUNT_POINT CONFIG_FS_MGMT_AUDIO_SUBDIR
#define FS_SYSTEM_PATH CONFIG_FS_MGMT_MOUNT_POINT CONFIG_FS_MGMT_SYSTEM_SUBDIR
/**
* @brief Initializes the filesystem management module.
*/
int fs_mgmt_init(void);
// /**
// * @brief Puts the QSPI flash into deep sleep mode to save power
// */
// int fs_pm_flash_suspend(void);
// /**
// * @brief Resumes the QSPI flash from deep sleep mode
// */
// int fs_pm_flash_resume(void);
/**
* @brief OP-Codes for the FS write thread
*/
enum fs_write_op {
FS_WRITE_OP_FILE_START,
FS_WRITE_OP_FILE_CHUNK,
FS_WRITE_OP_FILE_END,
FS_WRITE_OP_TAGS_START, // Schon mal vorgesehen
FS_WRITE_OP_FW_START, // Schon mal vorgesehen
FS_WRITE_OP_ABORT
};
/**
* @brief Structure representing a write message for the FS write thread
*/
struct fs_write_msg {
enum fs_write_op op;
uint8_t *slab_ptr; /* Basis-Pointer des Memory-Slabs (für k_mem_slab_free) */
uint16_t data_offset; /* Offset ab dem slab_ptr, wo die Nutzdaten beginnen */
uint16_t data_len; /* Länge der Nutzdaten */
uint32_t metadata; /* Zusatzinfo (Start: erwartete Dateigröße, End: erwartete CRC32) */
buzz_transport_reply_fn reply_cb; /* Callback für ACKs / Success / Error */
};
/**
* @brief Wrapper around fs_open that handles power management for the flash
@@ -114,4 +130,18 @@ int fs_mgmt_pm_mkdir_recursive(char *path);
*/
int fs_mgmt_pm_rm_recursive(char *path, size_t max_len);
/**
* @brief Gets the length of the audio data in a file, accounting for any metadata tags, ensuring the flash is active during the operation
* @param fp Pointer to an open fs_file_t structure representing the file
* @return Length of the audio data on success, negative error code on failure
*/
ssize_t fs_get_audio_data_len(struct fs_file_t *fp);
/**
* @brief Submits a write message to the FS write thread, which will handle writing data to the filestem asynchronously, ensuring the flash is active during the operation
* @param msg Pointer to the fs_write_msg structure containing the write operation details
* @return 0 on success, negative error code on failure
*/
int fs_mgmt_submit_write(struct fs_write_msg *msg);
#endif /* FS_MGMT_H */

View File

@@ -1,9 +1,12 @@
#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);
@@ -13,6 +16,9 @@ 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,
@@ -23,6 +29,34 @@ static struct fs_mount_t fs_storage_mnt = {
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
@@ -352,14 +386,15 @@ int fs_mgmt_pm_mkdir_recursive(char *path)
int fs_mgmt_init(void)
{
k_mutex_init(&flash_pm_lock);
if (!device_is_ready(flash_dev)) {
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)
@@ -370,4 +405,310 @@ int fs_mgmt_init(void)
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);