File upload. Yeah
This commit is contained in:
@@ -15,6 +15,7 @@ enum buzz_frame_type
|
||||
BUZZ_FRAME_RESPONSE = 0x10,
|
||||
BUZZ_FRAME_ACK = 0x11,
|
||||
BUZZ_FRAME_ERROR = 0x12,
|
||||
BUZZ_FRAME_SUCCESS = 0x13,
|
||||
|
||||
BUZZ_FRAME_FILE_START = 0x20,
|
||||
BUZZ_FRAME_FILE_CHUNK = 0x21,
|
||||
@@ -37,6 +38,10 @@ enum buzz_data_type
|
||||
|
||||
BUZZ_DATA_FILE_GET = 0x20,
|
||||
BUZZ_DATA_FILE_PUT = 0x21,
|
||||
BUZZ_DATA_TAGS_GET = 0x22,
|
||||
BUZZ_DATA_TAGS_PUT = 0x23,
|
||||
|
||||
BUZZ_DATA_FW_UPDATE = 0x30,
|
||||
|
||||
BUZZ_DATA_LS = 0x40,
|
||||
};
|
||||
@@ -62,6 +67,11 @@ struct __attribute__((packed)) buzz_resp_error
|
||||
uint16_t error_code; /* Bis 0xFF reserviert für Standard-Fehler, 0x100+ für spezifische Fehler */
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) buzz_resp_success
|
||||
{
|
||||
uint8_t data_type; /* Der Befehl, der erfolgreich war (z.B. BUZZ_DATA_FILE_PUT) */
|
||||
};
|
||||
|
||||
/* Payload für eine Standard-Anfrage (Request) */
|
||||
struct __attribute__((packed)) buzz_request_payload
|
||||
{
|
||||
@@ -142,4 +152,17 @@ void buzz_proto_buf_free(uint8_t **buf);
|
||||
/* Übergabe eines empfangenen Frames an den Protokoll-Thread */
|
||||
int buzz_proto_submit_frame(struct buzz_frame_msg *msg);
|
||||
|
||||
|
||||
/* Gibt die Anzahl der freien Slabs zurück (abzüglich Reserve) */
|
||||
uint16_t buzz_proto_get_free_rx_slabs(void);
|
||||
|
||||
/* Baut und sendet ein ACK Frame */
|
||||
void buzz_proto_send_ack(buzz_transport_reply_fn reply_cb, uint16_t credits);
|
||||
|
||||
/* Sendet einen Success-Frame unter Wiederverwendung eines bestehenden Slabs (Zero-Copy) */
|
||||
void buzz_proto_send_success_reusing_slab(buzz_transport_reply_fn reply_cb, uint8_t data_type, uint8_t *slab);
|
||||
|
||||
/* Sendet einen Error-Frame unter Wiederverwendung eines bestehenden Slabs (Zero-Copy) */
|
||||
void buzz_proto_send_error_reusing_slab(buzz_transport_reply_fn reply_cb, uint16_t error_code, uint8_t *slab);
|
||||
|
||||
#endif /* BUZZ_PROTO_H */
|
||||
@@ -113,10 +113,58 @@ static void send_stream_error(buzz_transport_reply_fn reply_cb, uint16_t error_c
|
||||
buzz_proto_buf_free(&buf);
|
||||
}
|
||||
|
||||
uint16_t buzz_proto_get_free_rx_slabs(void)
|
||||
{
|
||||
uint32_t free_slabs = k_mem_slab_num_free_get(&buzz_proto_slabs);
|
||||
return (free_slabs > 4) ? (uint16_t)(free_slabs - 4) : 0;
|
||||
}
|
||||
|
||||
void buzz_proto_send_ack(buzz_transport_reply_fn reply_cb, uint16_t credits)
|
||||
{
|
||||
if (!reply_cb || credits == 0)
|
||||
return;
|
||||
uint8_t *buf;
|
||||
if (buzz_proto_buf_alloc(&buf) == 0)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)buf;
|
||||
hdr->frame_type = BUZZ_FRAME_ACK;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_ack_payload));
|
||||
struct buzz_ack_payload *pl = (struct buzz_ack_payload *)(buf + sizeof(*hdr));
|
||||
pl->credits = sys_cpu_to_le16(credits);
|
||||
reply_cb(buf, sizeof(*hdr) + sizeof(*pl));
|
||||
buzz_proto_buf_free(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
void buzz_proto_send_success_reusing_slab(buzz_transport_reply_fn reply_cb, uint8_t data_type, uint8_t *slab)
|
||||
{
|
||||
if (!reply_cb || !slab)
|
||||
return;
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)slab;
|
||||
hdr->frame_type = BUZZ_FRAME_SUCCESS;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_success));
|
||||
struct buzz_resp_success *succ = (struct buzz_resp_success *)(slab + sizeof(*hdr));
|
||||
succ->data_type = data_type;
|
||||
reply_cb(slab, sizeof(*hdr) + sizeof(*succ));
|
||||
buzz_proto_buf_free(&slab);
|
||||
}
|
||||
|
||||
void buzz_proto_send_error_reusing_slab(buzz_transport_reply_fn reply_cb, uint16_t error_code, uint8_t *slab)
|
||||
{
|
||||
if (!reply_cb || !slab)
|
||||
return;
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)slab;
|
||||
hdr->frame_type = BUZZ_FRAME_ERROR;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_error));
|
||||
struct buzz_resp_error *err = (struct buzz_resp_error *)(slab + sizeof(*hdr));
|
||||
err->error_code = sys_cpu_to_le16(error_code);
|
||||
reply_cb(slab, sizeof(*hdr) + sizeof(*err));
|
||||
buzz_proto_buf_free(&slab);
|
||||
}
|
||||
|
||||
static void handle_proto_version_request(struct buzz_frame_msg *msg)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
|
||||
hdr->frame_type = BUZZ_FRAME_RESPONSE;
|
||||
|
||||
struct buzz_resp_proto_version *resp_data = (struct buzz_resp_proto_version *)(msg->data_ptr + sizeof(*hdr));
|
||||
@@ -124,10 +172,18 @@ static void handle_proto_version_request(struct buzz_frame_msg *msg)
|
||||
resp_data->data_type = BUZZ_DATA_PROTO_INFO;
|
||||
resp_data->version = sys_cpu_to_le16(BUZZ_PROTO_VERSION);
|
||||
|
||||
resp_data->max_chunk_size = sys_cpu_to_le16(CONFIG_BUZZ_PROTO_SLAB_SIZE - sizeof(struct buzz_proto_header));
|
||||
/* Dynamische Chunk-Größe basierend auf der aktuellen Transport-MTU berechnen */
|
||||
uint16_t slab_payload = CONFIG_BUZZ_PROTO_SLAB_SIZE - sizeof(struct buzz_proto_header);
|
||||
uint16_t transport_payload = 0;
|
||||
|
||||
if (msg->max_payload > sizeof(struct buzz_proto_header)) {
|
||||
transport_payload = msg->max_payload - sizeof(struct buzz_proto_header);
|
||||
}
|
||||
|
||||
uint16_t safe_chunk = MIN(slab_payload, transport_payload);
|
||||
resp_data->max_chunk_size = sys_cpu_to_le16(safe_chunk);
|
||||
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_proto_version));
|
||||
|
||||
uint16_t total_len = sizeof(struct buzz_proto_header) + sizeof(struct buzz_resp_proto_version);
|
||||
|
||||
if (msg->reply_cb)
|
||||
@@ -228,7 +284,7 @@ static void handle_ls_request(struct buzz_frame_msg *msg)
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_file_get_request(struct buzz_frame_msg *msg)
|
||||
static void handle_file_get_request(struct buzz_frame_msg *msg, bool only_tags)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
uint16_t payload_len = sys_le16_to_cpu(hdr->payload_length);
|
||||
@@ -251,7 +307,6 @@ static void handle_file_get_request(struct buzz_frame_msg *msg)
|
||||
memcpy(src_path, msg->data_ptr + sizeof(*hdr) + 1, path_len);
|
||||
src_path[path_len] = '\0';
|
||||
|
||||
// 1. Datei-Größe ermitteln
|
||||
struct fs_dirent entry;
|
||||
if (fs_mgmt_pm_stat(src_path, &entry) != 0)
|
||||
{
|
||||
@@ -260,7 +315,6 @@ static void handle_file_get_request(struct buzz_frame_msg *msg)
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Datei öffnen
|
||||
fs_file_t_init(&get_file_state.file);
|
||||
int rc = fs_mgmt_pm_open(&get_file_state.file, src_path, FS_O_READ);
|
||||
if (rc != 0)
|
||||
@@ -270,7 +324,31 @@ static void handle_file_get_request(struct buzz_frame_msg *msg)
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. State initialisieren
|
||||
uint32_t stream_size = entry.size;
|
||||
|
||||
if (only_tags)
|
||||
{
|
||||
ssize_t audio_len = fs_get_audio_data_len(&get_file_state.file);
|
||||
if (audio_len < 0)
|
||||
{
|
||||
LOG_ERR("Failed to get audio data len: %d", (int)audio_len);
|
||||
fs_mgmt_pm_close(&get_file_state.file);
|
||||
send_error_frame(msg, EIO);
|
||||
return;
|
||||
}
|
||||
|
||||
stream_size = entry.size - audio_len;
|
||||
|
||||
if (stream_size == 0)
|
||||
{
|
||||
fs_seek(&get_file_state.file, entry.size, FS_SEEK_SET);
|
||||
}
|
||||
else
|
||||
{
|
||||
fs_seek(&get_file_state.file, audio_len, FS_SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
current_stream = STREAM_FILE_GET;
|
||||
get_file_state.active = true;
|
||||
get_file_state.credits = 0;
|
||||
@@ -282,12 +360,11 @@ static void handle_file_get_request(struct buzz_frame_msg *msg)
|
||||
|
||||
LOG_INF("Started FILE_GET stream for '%s' (%u bytes)", src_path, entry.size);
|
||||
|
||||
// 4. FILE_START Frame senden
|
||||
hdr->frame_type = BUZZ_FRAME_FILE_START;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_file_start_payload));
|
||||
|
||||
struct buzz_file_start_payload *start_pl = (struct buzz_file_start_payload *)(msg->data_ptr + sizeof(*hdr));
|
||||
start_pl->total_size = sys_cpu_to_le32(entry.size);
|
||||
start_pl->total_size = sys_cpu_to_le32(stream_size);
|
||||
|
||||
if (msg->reply_cb)
|
||||
{
|
||||
@@ -332,7 +409,7 @@ static void process_file_get_stream(void)
|
||||
return;
|
||||
}
|
||||
|
||||
// Chunk Size berechnen
|
||||
// Chunk Size berechnen
|
||||
uint16_t max_chunk_size = MIN(
|
||||
get_file_state.max_payload - sizeof(*hdr),
|
||||
CONFIG_BUZZ_PROTO_SLAB_SIZE - sizeof(struct buzz_proto_header));
|
||||
@@ -434,18 +511,96 @@ static void handle_request(struct buzz_frame_msg *msg)
|
||||
LOG_DBG("Received Proto Version Request");
|
||||
handle_proto_version_request(msg);
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_FS_INFO:
|
||||
LOG_DBG("Received FS Info Request");
|
||||
handle_fs_info_request(msg);
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_LS:
|
||||
LOG_DBG("Received LS Request");
|
||||
handle_ls_request(msg);
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_FILE_GET:
|
||||
LOG_DBG("Received FILE_GET Request");
|
||||
handle_file_get_request(msg);
|
||||
handle_file_get_request(msg, false);
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_FILE_PUT:
|
||||
LOG_DBG("Received FILE_PUT Request");
|
||||
if (payload_len < sizeof(struct buzz_request_payload) + sizeof(uint32_t) + 1)
|
||||
{
|
||||
send_error_frame(msg, EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_stream != STREAM_IDLE)
|
||||
{
|
||||
LOG_WRN("Stream active, rejecting FILE_PUT request");
|
||||
send_error_frame(msg, EBUSY);
|
||||
return;
|
||||
}
|
||||
|
||||
struct fs_write_msg write_req = {
|
||||
.op = FS_WRITE_OP_FILE_START,
|
||||
.slab_ptr = msg->data_ptr,
|
||||
.data_offset = sizeof(*hdr) + sizeof(struct buzz_request_payload) + sizeof(uint32_t),
|
||||
.data_len = payload_len - sizeof(struct buzz_request_payload) - sizeof(uint32_t),
|
||||
.metadata = sys_get_le32(msg->data_ptr + sizeof(*hdr) + sizeof(struct buzz_request_payload)),
|
||||
.reply_cb = msg->reply_cb};
|
||||
|
||||
if (fs_mgmt_submit_write(&write_req) == 0)
|
||||
{
|
||||
current_stream = STREAM_FILE_PUT; /* WICHTIG: Status blockieren */
|
||||
msg->data_ptr = NULL; /* Ownership an FS-Thread übertragen */
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error_frame(msg, EBUSY);
|
||||
}
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_TAGS_PUT:
|
||||
LOG_DBG("Received TAGS_PUT Request");
|
||||
if (payload_len < sizeof(struct buzz_request_payload) + sizeof(uint32_t) + 1)
|
||||
{
|
||||
send_error_frame(msg, EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_stream != STREAM_IDLE)
|
||||
{
|
||||
LOG_WRN("Stream active, rejecting TAGS_PUT request");
|
||||
send_error_frame(msg, EBUSY);
|
||||
return;
|
||||
}
|
||||
|
||||
struct fs_write_msg tags_req = {
|
||||
.op = FS_WRITE_OP_TAGS_START,
|
||||
.slab_ptr = msg->data_ptr,
|
||||
.data_offset = sizeof(*hdr) + sizeof(struct buzz_request_payload) + sizeof(uint32_t),
|
||||
.data_len = payload_len - sizeof(struct buzz_request_payload) - sizeof(uint32_t),
|
||||
.metadata = sys_get_le32(msg->data_ptr + sizeof(*hdr) + sizeof(struct buzz_request_payload)),
|
||||
.reply_cb = msg->reply_cb
|
||||
};
|
||||
|
||||
if (fs_mgmt_submit_write(&tags_req) == 0)
|
||||
{
|
||||
current_stream = STREAM_FILE_PUT; /* Blockiert den Stream für weitere Requests */
|
||||
msg->data_ptr = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error_frame(msg, EBUSY);
|
||||
}
|
||||
break;
|
||||
|
||||
case BUZZ_DATA_TAGS_GET:
|
||||
LOG_DBG("Received TAGS_GET Request");
|
||||
handle_file_get_request(msg, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_WRN("Unknown request data_type: 0x%02x", req_data->data_type);
|
||||
send_error_frame(msg, EINVAL);
|
||||
@@ -580,7 +735,64 @@ static void buzz_proto_thread_fn(void *p1, void *p2, void *p3)
|
||||
break;
|
||||
|
||||
case BUZZ_FRAME_FILE_CHUNK:
|
||||
send_error_frame(&msg, ENOSYS);
|
||||
if (current_stream != STREAM_FILE_PUT)
|
||||
{
|
||||
send_error_frame(&msg, EBADMSG);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
struct fs_write_msg chunk_req = {
|
||||
.op = FS_WRITE_OP_FILE_CHUNK,
|
||||
.slab_ptr = msg.data_ptr,
|
||||
.data_offset = sizeof(*hdr),
|
||||
.data_len = sys_le16_to_cpu(hdr->payload_length),
|
||||
.reply_cb = msg.reply_cb};
|
||||
|
||||
if (fs_mgmt_submit_write(&chunk_req) == 0)
|
||||
{
|
||||
msg.data_ptr = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error_frame(&msg, EBUSY);
|
||||
}
|
||||
buzz_proto_buf_free(&msg.data_ptr); /* Tut nichts, wenn msg.data_ptr == NULL */
|
||||
break;
|
||||
|
||||
case BUZZ_FRAME_FILE_END:
|
||||
if (current_stream != STREAM_FILE_PUT)
|
||||
{
|
||||
send_error_frame(&msg, EBADMSG);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg.length >= sizeof(*hdr) + sizeof(struct buzz_file_end_payload))
|
||||
{
|
||||
struct buzz_file_end_payload *end_pl = (struct buzz_file_end_payload *)(msg.data_ptr + sizeof(*hdr));
|
||||
struct fs_write_msg end_req = {
|
||||
.op = FS_WRITE_OP_FILE_END,
|
||||
.slab_ptr = msg.data_ptr,
|
||||
.data_offset = 0,
|
||||
.data_len = 0,
|
||||
.metadata = sys_le32_to_cpu(end_pl->crc32),
|
||||
.reply_cb = msg.reply_cb};
|
||||
|
||||
if (fs_mgmt_submit_write(&end_req) == 0)
|
||||
{
|
||||
msg.data_ptr = NULL;
|
||||
current_stream = STREAM_IDLE; /* Stream wieder freigeben */
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error_frame(&msg, EBUSY);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error_frame(&msg, EINVAL);
|
||||
}
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user