671 lines
27 KiB
C
671 lines
27 KiB
C
/* USER CODE BEGIN Header */
|
||
/**
|
||
******************************************************************************
|
||
* File Name : freertos.c
|
||
* Description : Code for freertos applications
|
||
******************************************************************************
|
||
* @attention
|
||
*
|
||
* Copyright (c) 2026 STMicroelectronics.
|
||
* All rights reserved.
|
||
*
|
||
* This software is licensed under terms that can be found in the LICENSE file
|
||
* in the root directory of this software component.
|
||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||
*
|
||
******************************************************************************
|
||
*/
|
||
/* USER CODE END Header */
|
||
|
||
/* Includes ------------------------------------------------------------------*/
|
||
#include "FreeRTOS.h"
|
||
#include "task.h"
|
||
#include "main.h"
|
||
#include "cmsis_os.h"
|
||
|
||
/* Private includes ----------------------------------------------------------*/
|
||
/* USER CODE BEGIN Includes */
|
||
#include "test_config.h" /* 套件选择:STORAGE 套件下 defaultTask 让出存储所有权 */
|
||
#include <string.h>
|
||
#include "net_task.h"
|
||
#include "ch395f.h"
|
||
#include "dbg_log.h"
|
||
#include "net_socket.h"
|
||
#include "usart.h"
|
||
#include "ff.h"
|
||
#include "gd5f2gq5ue.h"
|
||
#include "nand_ftl.h"
|
||
#include "lftpd.h"
|
||
#include "../test/ch395f_test_task.h"
|
||
#include "../test/ch395f_test.h" /* ENABLE_NET_LAYER_TESTS:netTask 条件创建 */
|
||
#include "time_sync.h"
|
||
#include "sys_events.h"
|
||
#include "sys_log.h"
|
||
#include "net_cfg.h"
|
||
#include "cfg_file.h"
|
||
/* USER CODE END Includes */
|
||
|
||
/* Private typedef -----------------------------------------------------------*/
|
||
/* USER CODE BEGIN PTD */
|
||
|
||
/* USER CODE END PTD */
|
||
|
||
/* Private define ------------------------------------------------------------*/
|
||
/* USER CODE BEGIN PD */
|
||
/* 栈水位观测节奏:首次报告须晚于 bring-up 深链(mount/mkfs/探针/cfg/syslog),
|
||
* 之后每周期只判定"是否出现更深峰值",无新峰值不打印(保持串口静默窗口) */
|
||
#define STACK_PROBE_FIRST_MS 5000u
|
||
#define STACK_PROBE_PERIOD_MS 2000u
|
||
/* E-31 自愈策略(2026-09-21 审计 P0 修复):启动期存储挂载失败后,
|
||
* 主循环按 2s 周期重跑完整 bring-up 的次数上限;耗尽则维持无存储降级
|
||
* (自检已上报、卷保留、不重格式化),等下次上电重新 resume */
|
||
#define STORAGE_BRINGUP_RETRIES 3u
|
||
/* USER CODE END PD */
|
||
|
||
/* Private macro -------------------------------------------------------------*/
|
||
/* USER CODE BEGIN PM */
|
||
|
||
/* USER CODE END PM */
|
||
|
||
/* Private variables ---------------------------------------------------------*/
|
||
/* USER CODE BEGIN Variables */
|
||
/* 存储测试变量(g_storage_stats / s_perf_buf)已迁至 test/storage_test_task.c */
|
||
osThreadId_t timeSyncTaskHandle;
|
||
const osThreadAttr_t timeSyncTask_attributes = {
|
||
.name = "timeSyncTask",
|
||
.stack_size = 512 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* USER CODE END Variables */
|
||
/* Definitions for defaultTask */
|
||
osThreadId_t defaultTaskHandle;
|
||
const osThreadAttr_t defaultTask_attributes = {
|
||
.name = "defaultTask",
|
||
.stack_size = 1024 * 4, /* 512→1024: 该任务扛 mount+mkfs+FAT探针+cfg加载+syslog写+
|
||
* 配置引擎(全深层 FatFS I/O),2KB 实测峰值溢出——开启
|
||
* configCHECK_FOR_STACK_OVERFLOW=2 后被钩子抓到(2026-09-21)。
|
||
* 此前静默溢出腐蚀堆相邻块。改 .ioc 前须同步此值防 CubeMX 覆盖 */
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* Definitions for netTask */
|
||
osThreadId_t netTaskHandle;
|
||
const osThreadAttr_t netTask_attributes = {
|
||
.name = "netTask",
|
||
.stack_size = 1024 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* Definitions for ftpTask */
|
||
osThreadId_t ftpTaskHandle;
|
||
const osThreadAttr_t ftpTask_attributes = {
|
||
.name = "ftpTask",
|
||
.stack_size = 1024 * 4,
|
||
.priority = (osPriority_t) osPriorityNormal,
|
||
};
|
||
/* Definitions for testTask */
|
||
osThreadId_t testTaskHandle;
|
||
const osThreadAttr_t testTask_attributes = {
|
||
.name = "testTask",
|
||
.stack_size = 2048 * 4, /* 1024→2048: 存储套件 deep call chain + 局部缓冲,4KB 实测溢出(2026-09-20) */
|
||
.priority = (osPriority_t) osPriorityBelowNormal,
|
||
};
|
||
/* Definitions for uiTask */
|
||
osThreadId_t uiTaskHandle;
|
||
const osThreadAttr_t uiTask_attributes = {
|
||
.name = "uiTask",
|
||
.stack_size = 4096 * 4,
|
||
.priority = (osPriority_t) osPriorityBelowNormal,
|
||
};
|
||
/* Definitions for netMsgQueue */
|
||
osMessageQueueId_t netMsgQueueHandle;
|
||
const osMessageQueueAttr_t netMsgQueue_attributes = {
|
||
.name = "netMsgQueue"
|
||
};
|
||
/* Definitions for sys_evt */
|
||
osEventFlagsId_t sys_evtHandle;
|
||
const osEventFlagsAttr_t sys_evt_attributes = {
|
||
.name = "sys_evt"
|
||
};
|
||
|
||
/* Private function prototypes -----------------------------------------------*/
|
||
/* USER CODE BEGIN FunctionPrototypes */
|
||
void ui_task_func(void *argument);
|
||
static void stack_probe_poll(void); /* 栈水位观测(可观测性,实现见文件末尾) */
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
static void default_task_net_cfg_save_notify(void);
|
||
static FRESULT storage_bring_up(FATFS *fs); /* 存储 bring-up(实现见文件末尾) */
|
||
#endif
|
||
/* USER CODE END FunctionPrototypes */
|
||
|
||
void StartDefaultTask(void *argument);
|
||
void StartNetTask(void *argument);
|
||
void StartFtpTask(void *argument);
|
||
void start_test_task(void *argument);
|
||
void StartUiTask(void *argument);
|
||
|
||
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
|
||
|
||
/**
|
||
* @brief FreeRTOS initialization
|
||
* @param None
|
||
* @retval None
|
||
*/
|
||
void MX_FREERTOS_Init(void) {
|
||
/* USER CODE BEGIN Init */
|
||
|
||
/* USER CODE END Init */
|
||
|
||
/* USER CODE BEGIN RTOS_MUTEX */
|
||
/* add mutexes, ... */
|
||
/* USER CODE END RTOS_MUTEX */
|
||
|
||
/* USER CODE BEGIN RTOS_SEMAPHORES */
|
||
/* add semaphores, ... */
|
||
/* USER CODE END RTOS_SEMAPHORES */
|
||
|
||
/* USER CODE BEGIN RTOS_TIMERS */
|
||
/* start timers, add new ones, ... */
|
||
/* USER CODE END RTOS_TIMERS */
|
||
|
||
/* Create the queue(s) */
|
||
/* creation of netMsgQueue */
|
||
netMsgQueueHandle = osMessageQueueNew (8, sizeof(net_msg_t), &netMsgQueue_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_QUEUES */
|
||
/* add queues, ... */
|
||
/* USER CODE END RTOS_QUEUES */
|
||
|
||
/* Create the thread(s) */
|
||
/* creation of defaultTask */
|
||
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
|
||
|
||
/* creation of netTask */
|
||
netTaskHandle = osThreadNew(StartNetTask, NULL, &netTask_attributes);
|
||
|
||
/* creation of ftpTask */
|
||
ftpTaskHandle = osThreadNew(StartFtpTask, NULL, &ftpTask_attributes);
|
||
|
||
/* creation of testTask */
|
||
testTaskHandle = osThreadNew(start_test_task, NULL, &testTask_attributes);
|
||
|
||
/* creation of uiTask */
|
||
uiTaskHandle = osThreadNew(StartUiTask, NULL, &uiTask_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_THREADS */
|
||
/* netTask = 基础线程:上方生成区的实例是全构建唯一实例(P1-6:旧
|
||
* #ifdef ENABLE_NET_LAYER_TESTS 的重复创建已删除)。
|
||
* - 产品 / NET 层测试模式:正常运行 bring_up + net_poll
|
||
* - CH395F 硬件层测试模式:net_task_func 入口自挂起(testTask 独占 CH395F) */
|
||
|
||
/* 本地时间同步任务(PC 推送 TCP):依赖 netTask 处理消息队列 */
|
||
timeSyncTaskHandle = osThreadNew(time_sync_task, NULL, &timeSyncTask_attributes);
|
||
/* USER CODE END RTOS_THREADS */
|
||
|
||
/* Create the event(s) */
|
||
/* creation of sys_evt */
|
||
sys_evtHandle = osEventFlagsNew(&sys_evt_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_EVENTS */
|
||
/* add events, ... */
|
||
/* USER CODE END RTOS_EVENTS */
|
||
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartDefaultTask */
|
||
/**
|
||
* @brief Function implementing the defaultTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartDefaultTask */
|
||
void StartDefaultTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartDefaultTask */
|
||
(void)argument;
|
||
|
||
/* 系统启动期挂载 NAND 上的 FatFS 卷,供 FTP 及其它模块访问。
|
||
* f_mount(forced=1) 会触发 disk_initialize() → nand_ftl_init()
|
||
* (dhara_map_resume)。此前默认构建从未挂载,导致 FTP 的
|
||
* LIST/STOR/RETR 全部返回 550(f_opendir 失败)。
|
||
* 完整 bring-up 序列(挂载 + 空卷自动建卷 + 健康探针 + 自愈重格式化
|
||
* + 日志挂载 + 配置加载 + 就绪屏障)实现于 storage_bring_up()。
|
||
*
|
||
* E-31(2026-09-21 审计,P0 修复):dhara_map_resume 失败不再被当
|
||
* "新卷"静默重格式化(扫描中任何一次瞬态毛刺都会销毁全部用户数据)——
|
||
* nand_ftl 仅把 DHARA_E_TOO_BAD(真空白)判为新卷,其余失败使
|
||
* disk_initialize 失败 → f_mount 失败 → 下方自检上报并保留卷;
|
||
* 瞬态失败由主循环有界重试(STORAGE_BRINGUP_RETRIES 次 × 2s),
|
||
* 成功后补跑"存储就绪"序列(日志挂载/配置加载/屏障置位,均幂等)。
|
||
* 探针/重格式化自愈策略仍在 storage_bring_up 内(2026-09-20 修复,
|
||
* 小写两击确认、FR_INVALID_NAME 不入重格式化集、写保护类只上报,
|
||
* 详见该函数注释)。 */
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
/* E-31 自愈:存储 bring-up 剩余重试次数(0 = 已就绪或已耗尽);
|
||
* 须在启动块之前声明(启动块失败路径写入、主循环读取) */
|
||
uint32_t storage_retry = 0u;
|
||
#endif
|
||
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
/* STORAGE 套件构建下 defaultTask 完全跳过存储 bring-up(挂载/配置/日志盘写),
|
||
* 卷由 testTask 的 storage_test_run 独占——测试含 nand_ftl_deinit 掉电模拟,
|
||
* 与任何后台文件写入并发都会互相踩坏 FTL/FatFs 状态(2026-09-20 台架实证:
|
||
* 双写并发导致 mkfs 半途冻结 + BBT 被误写) */
|
||
static FATFS default_fatfs;
|
||
FRESULT fr = storage_bring_up(&default_fatfs);
|
||
if (fr != FR_OK) {
|
||
/* 存储不可写:自检故障仅能滞留待发队列(串口已告警) */
|
||
syslog_post(SYSLOG_SELFTEST, "filesystem fault");
|
||
/* E-31 自愈:启动主循环中的有界重试(每 2s 一次) */
|
||
storage_retry = STORAGE_BRINGUP_RETRIES;
|
||
}
|
||
|
||
/* 注册"保存网络配置"请求通知:UI(uiTask)投递 → 置事件 → 本任务立即执行写盘
|
||
* (配置引擎属主 = 本任务;UI 保存不再直接触碰配置文件引擎,P1-3) */
|
||
net_cfg_set_save_req_cb(default_task_net_cfg_save_notify);
|
||
#else
|
||
/* STORAGE 套件构建:挂载/配置引擎/日志盘写全部跳过,存储由 testTask 独占 */
|
||
DBG_INFO("[FS] mount/cfg skipped (STORAGE suite owns storage)");
|
||
#endif
|
||
|
||
/* 配置 poll 周期计数(每 4 次循环 = 2s 轮询一次,与引擎文档/模拟器一致) */
|
||
uint32_t cfg_poll_cnt = 0u;
|
||
|
||
for (;;) {
|
||
/* 等待"保存网络配置"请求:有请求立即唤醒;无请求 500ms 后返回
|
||
* (取代原 osDelay(500):循环节奏不变,保存响应零空转) */
|
||
if (sys_evtHandle != NULL) {
|
||
(void)osEventFlagsWait(sys_evtHandle, SYS_EVT_CFG_SAVE, osFlagsWaitAny, 500u);
|
||
}
|
||
/* 执行待处理保存(配置引擎属主:写盘 + 成功后立即应用);无请求 = 空操作 */
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
net_cfg_save_run();
|
||
#endif
|
||
/* 配置文件变更轮询(2s 周期;沉降去抖在引擎内) */
|
||
if ((cfg_poll_cnt++ & 3u) == 0u) {
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
/* E-31 自愈:启动期挂载失败时,每 2s 重跑完整 bring-up——
|
||
* 瞬态毛刺(如 dhara resume 扫描的瞬时 SPI 故障)通常第二次
|
||
* 即恢复;成功后由 storage_bring_up 补跑启动时跳过的
|
||
* "存储就绪"序列(日志挂载/配置加载/屏障置位,均幂等);
|
||
* 持续性故障耗尽重试则维持无存储降级(自检已在启动/耗尽时
|
||
* 上报,卷保留、不重格式化,等下次上电重新 resume) */
|
||
if (storage_retry > 0u) {
|
||
FRESULT rfr = storage_bring_up(&default_fatfs);
|
||
if (rfr == FR_OK) {
|
||
storage_retry = 0u;
|
||
DBG_INFO("[FS] storage bring-up OK on retry");
|
||
} else if (--storage_retry == 0u) {
|
||
DBG_ERROR("[FS] storage retries exhausted (fr=%d), "
|
||
"storage disabled", (int)rfr);
|
||
syslog_post(SYSLOG_SELFTEST, "filesystem fault");
|
||
}
|
||
}
|
||
cfg_file_poll();
|
||
#endif
|
||
}
|
||
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
|
||
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
|
||
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
/* 排空运行期日志(上电/重启分类与自检故障落盘) */
|
||
syslog_drain();
|
||
#endif
|
||
/* 栈水位观测:首次 5s 后报告,此后每 2s 采样、出现更深峰值才打印 */
|
||
stack_probe_poll();
|
||
}
|
||
/* USER CODE END StartDefaultTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartNetTask */
|
||
/**
|
||
* @brief Function implementing the netTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartNetTask */
|
||
void StartNetTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartNetTask */
|
||
net_task_func(argument);
|
||
/* USER CODE END StartNetTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartFtpTask */
|
||
/**
|
||
* @brief Function implementing the ftpTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartFtpTask */
|
||
__weak void StartFtpTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartFtpTask */
|
||
/* 等网络 + 存储都真正就绪后再 listen,取代拍脑袋的 7s 魔法延时 */
|
||
/*
|
||
sys_evtHandle:等待哪个事件组
|
||
SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY:要等哪几位(1u<<0 网络、1u<<1 存储)
|
||
osFlagsWaitAll:请求的位全部置位才唤醒(即"网络且存储都就绪")
|
||
osWaitForever:无限期等待,不设超时——任务会挂起(让出 CPU,零占用),直到生产者置位对应位才被唤醒
|
||
*/
|
||
if (sys_evtHandle != NULL) {
|
||
/* osFlagsNoClear 必须带:就绪位是**状态**不是命令,消费型等待会把位清掉,
|
||
* 导致后面 WaitAll(两位) 的等待者永远等不到(2026-09-21 实测:netTask 先
|
||
* 消费 STORAGE_READY、uiTask/timeSync 再消费 NET_READY → 本任务永不唤醒、
|
||
* FTP 21 端口从未监听)。 */
|
||
(void)osEventFlagsWait(sys_evtHandle,
|
||
SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY,
|
||
osFlagsWaitAll | osFlagsNoClear, osWaitForever);
|
||
}
|
||
lftpd_t ftp;
|
||
lftpd_start("/", 21, &ftp);
|
||
/* 广播"FTP 服务就绪"(预留给依赖 FTP 的后续功能) */
|
||
if (sys_evtHandle != NULL) {
|
||
osEventFlagsSet(sys_evtHandle, SYS_EVT_FTP_READY);
|
||
}
|
||
/* USER CODE END StartFtpTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_start_test_task */
|
||
/**
|
||
* @brief Function implementing the testTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_start_test_task */
|
||
__weak void start_test_task(void *argument)
|
||
{
|
||
/* USER CODE BEGIN start_test_task */
|
||
/* Infinite loop */
|
||
for(;;)
|
||
{
|
||
osDelay(1);
|
||
}
|
||
/* USER CODE END start_test_task */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartUiTask */
|
||
/**
|
||
* @brief Function implementing the uiTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartUiTask */
|
||
void StartUiTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartUiTask */
|
||
ui_task_func(argument);
|
||
/* USER CODE END StartUiTask */
|
||
}
|
||
|
||
/* Private application code --------------------------------------------------*/
|
||
/* USER CODE BEGIN Application */
|
||
/*
|
||
* 栈水位观测(可观测性,2026-09-21)
|
||
* 数据源:任务栈"历史最小剩余"——内核按特征值填充法统计(与
|
||
* configCHECK_FOR_STACK_OVERFLOW=2 同一机制),osThreadGetStackSpace() 取剩余
|
||
* 字节,配置字节取各任务 *_attributes.stack_size(唯一定义处,避免常量重复;
|
||
* 本版 CMSIS 包装只有 GetStackSpace 实现,无 osThreadGetStackSize)。
|
||
* 峰值占用 = 配置 - 剩余。
|
||
* 定栈规则(docs/内存管理/RAM使用分析与优化方案.md):新栈 = 峰值*1.5 + 1KB ISR
|
||
* 余量,并要求 free_min >= 新栈*30% 才算合格。
|
||
* 节奏:首次 STACK_PROBE_FIRST_MS 后报告(bring-up 深链已跑过),此后每
|
||
* STACK_PROBE_PERIOD_MS 采样一次,只在出现更深峰值时打印——既不改串口静默
|
||
* 窗口,也不漏掉之后才走到的更深分支(如卷修复/配置保存路径)。
|
||
* 输出(纯 ASCII,便于 @test 采集):
|
||
* [STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
||
* [STACK] heap free=<B> min_ever=<B>
|
||
* 未覆盖 idle/timer 任务(内核静态小栈、纯循环,未取句柄)。
|
||
*/
|
||
typedef struct {
|
||
const char *p_name;
|
||
const osThreadId_t *p_id;
|
||
const osThreadAttr_t *p_attr; /* 配置栈大小来源(stack_size) */
|
||
} stack_probe_task_t;
|
||
|
||
static const stack_probe_task_t s_stack_probe[] = {
|
||
{ "default", &defaultTaskHandle, &defaultTask_attributes },
|
||
{ "net", &netTaskHandle, &netTask_attributes },
|
||
{ "ftp", &ftpTaskHandle, &ftpTask_attributes },
|
||
{ "test", &testTaskHandle, &testTask_attributes },
|
||
{ "ui", &uiTaskHandle, &uiTask_attributes },
|
||
{ "timesync", &timeSyncTaskHandle, &timeSyncTask_attributes },
|
||
};
|
||
|
||
#define STACK_PROBE_COUNT (sizeof(s_stack_probe) / sizeof(s_stack_probe[0]))
|
||
|
||
static void stack_probe_poll(void)
|
||
{
|
||
static uint32_t s_free_min[STACK_PROBE_COUNT]; /* 历史最小剩余(B),0=未采样 */
|
||
static uint32_t s_last_tick;
|
||
static uint8_t s_reported;
|
||
const TickType_t now = xTaskGetTickCount();
|
||
uint32_t i;
|
||
uint8_t deeper = 0u;
|
||
|
||
if (s_reported == 0u) {
|
||
if (now < pdMS_TO_TICKS(STACK_PROBE_FIRST_MS)) {
|
||
return;
|
||
}
|
||
} else if ((uint32_t)(now - s_last_tick) < (uint32_t)pdMS_TO_TICKS(STACK_PROBE_PERIOD_MS)) {
|
||
return;
|
||
}
|
||
s_last_tick = (uint32_t)now;
|
||
|
||
for (i = 0u; i < (uint32_t)STACK_PROBE_COUNT; i++) {
|
||
const osThreadId_t th = *(s_stack_probe[i].p_id);
|
||
uint32_t free_now;
|
||
|
||
if (th == NULL) {
|
||
continue; /* 任务未创建 */
|
||
}
|
||
free_now = osThreadGetStackSpace(th);
|
||
if (free_now == 0u) {
|
||
continue; /* 句柄不可用:跳过 */
|
||
}
|
||
if ((s_free_min[i] == 0u) || (free_now < s_free_min[i])) {
|
||
s_free_min[i] = free_now; /* 刷新历史最小(= 最深峰值) */
|
||
deeper = 1u;
|
||
}
|
||
}
|
||
|
||
if ((s_reported != 0u) && (deeper == 0u)) {
|
||
return; /* 无新峰值:静默 */
|
||
}
|
||
s_reported = 1u;
|
||
|
||
for (i = 0u; i < (uint32_t)STACK_PROBE_COUNT; i++) {
|
||
const uint32_t free_min = s_free_min[i];
|
||
const uint32_t cfg = (uint32_t)s_stack_probe[i].p_attr->stack_size;
|
||
|
||
if (free_min == 0u) {
|
||
continue;
|
||
}
|
||
if (cfg == 0u) {
|
||
continue;
|
||
}
|
||
DBG_INFO("[STACK] %s peak_used=%lu/%lu free_min=%lu (%lu%%)",
|
||
s_stack_probe[i].p_name,
|
||
(unsigned long)(cfg - free_min), (unsigned long)cfg,
|
||
(unsigned long)free_min,
|
||
(unsigned long)((free_min * 100u) / cfg));
|
||
}
|
||
DBG_INFO("[STACK] heap free=%lu min_ever=%lu",
|
||
(unsigned long)xPortGetFreeHeapSize(),
|
||
(unsigned long)xPortGetMinimumEverFreeHeapSize());
|
||
}
|
||
|
||
/*
|
||
* 存储 bring-up:挂载 + 空卷自动建卷 + 卷健康探针(两击)+ 自愈重格式化
|
||
* + 日志挂载 + 配置加载 + "存储就绪"屏障。
|
||
*
|
||
* 由 defaultTask 调用:启动期一次;若失败,主循环每 2s 重试至多
|
||
* STORAGE_BRINGUP_RETRIES 次(E-31 自愈策略,2026-09-21):dhara_map_resume
|
||
* 失败分类修复后(nand_ftl.c 仅真空白判新卷,其余失败保留卷并返回
|
||
* disk_initialize 失败),重试让瞬态毛刺(SPI 瞬时故障/扫描窗口抖动)
|
||
* 在数秒内免重启恢复,成功时补跑启动期跳过的就绪序列(日志挂载/配置
|
||
* 加载/屏障置位,三者均幂等、可重跑);持续故障耗尽重试后进入无存储
|
||
* 降级态(自检已上报、卷保留、不重格式化,等下次上电重新 resume)。
|
||
*
|
||
* 探针/重格式化策略(2026-09-20 台架实证修复,原启动块注释迁入):
|
||
* 小写两击确认防 FTL 瞬态抖动误触发;仅结构性错误集合(已排除
|
||
* FR_INVALID_NAME)触发自愈重格式化;写保护/卷锁/参数类只上报不重格式化
|
||
* (防对写锁卷整卷擦除)。
|
||
*/
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
static FRESULT storage_bring_up(FATFS *fs)
|
||
{
|
||
FRESULT fr = f_mount(fs, "", 1);
|
||
|
||
if (fr == FR_NO_FILESYSTEM) {
|
||
/* 卷为空(如恢复/坏块重建后清空):自动格式化建卷
|
||
* (MKFS_PARM 与存储测试一致:FM_FAT32 / 无分区表) */
|
||
DBG_INFO("[FS] no FAT volume, formatting NAND...");
|
||
static uint8_t fs_work[FF_MAX_SS];
|
||
MKFS_PARM opts = { FM_FAT32, 0, 0, 0, 0 };
|
||
/* 格式化前清空 FTL map 树:新卷从空树开始写,journal
|
||
* 不与旧卷残留共存(结构卫生)。注:这不是"无限重格式化"的根因——
|
||
* 真根因是 FatFs 探针文件名非法(见下方探针注释)。 */
|
||
(void)nand_ftl_format();
|
||
fr = f_mkfs("", &opts, fs_work, sizeof(fs_work));
|
||
if (fr == FR_OK) {
|
||
fr = f_mount(fs, "", 1);
|
||
} else {
|
||
DBG_ERROR("[FS] f_mkfs failed (fr=%d)", (int)fr);
|
||
}
|
||
}
|
||
|
||
if (fr == FR_OK) {
|
||
/* 探针:/fschk.tmp 小写一次,验证 FAT 链真实可用。本移植
|
||
* f_write 簇分配失败返回 FR_OK+短写 → 必须校验写回字节数;短写按
|
||
* FR_DENIED 归一化(落入下方结构性集合触发重格式化)。
|
||
* ⚠ 事故教训(2026-09-20 台架实证,"无限重格式化"真根因):本移植
|
||
* FF_USE_LFN=0,ff.c create_short 把 ".fschk" 点后的 FSCHK 当扩展域
|
||
* 解析(5 字符 > 3 上限)→ f_open 恒返 FR_INVALID_NAME(6)(纯 RAM
|
||
* 判定、不碰盘)→ 曾误入下方结构性错误集合 → 每 boot 假失败 →
|
||
* 无限重格式化。探针名必须严格 8.3:名≤8、扩展≤3、不以点开头。 */
|
||
#define FS_VOL_PROBE(h) do { \
|
||
FIL __chk; \
|
||
static const uint8_t __chkbuf[8] = { 0 }; \
|
||
UINT __bw = 0u; \
|
||
FRESULT __wr; \
|
||
(h) = f_open(&__chk, "/fschk.tmp", FA_WRITE | FA_OPEN_ALWAYS); \
|
||
if ((h) == FR_OK) { \
|
||
__wr = f_write(&__chk, __chkbuf, (UINT)sizeof(__chkbuf), &__bw); \
|
||
(void)f_close(&__chk); \
|
||
if (__wr == FR_OK && __bw == (UINT)sizeof(__chkbuf)) { \
|
||
(h) = FR_OK; \
|
||
(void)f_unlink("/fschk.tmp"); \
|
||
} else { \
|
||
(h) = (__wr != FR_OK) ? __wr : FR_DENIED; \
|
||
} \
|
||
} \
|
||
} while (0)
|
||
FRESULT hfr;
|
||
FS_VOL_PROBE(hfr);
|
||
if (hfr != FR_OK) {
|
||
FS_VOL_PROBE(hfr); /* 两击确认 */
|
||
}
|
||
if (hfr != FR_OK) {
|
||
/* ⚠ FR_INVALID_NAME 绝不可入本集合:它是路径名语法错误(纯 RAM 判定、
|
||
* 不碰盘),与卷健康无关(曾误入 → "无限重格式化"事故) */
|
||
if (hfr == FR_DENIED || hfr == FR_INVALID_OBJECT ||
|
||
hfr == FR_INT_ERR || hfr == FR_DISK_ERR ||
|
||
hfr == FR_NO_FILE || hfr == FR_NO_PATH ||
|
||
hfr == FR_NO_FILESYSTEM) {
|
||
DBG_INFO("[FS] volume FAT invalid (fr=%d), reformatting...",
|
||
(int)hfr);
|
||
f_mount(NULL, "", 0);
|
||
/* 格式化前清空 FTL map 树(同首启路径:新卷从空树写起) */
|
||
(void)nand_ftl_format();
|
||
{
|
||
static uint8_t fs_work2[FF_MAX_SS];
|
||
MKFS_PARM opts2 = { FM_FAT32, 0, 0, 0, 0 };
|
||
fr = f_mkfs("", &opts2, fs_work2, sizeof(fs_work2));
|
||
if (fr == FR_OK) {
|
||
fr = f_mount(fs, "", 1);
|
||
}
|
||
}
|
||
if (fr == FR_OK) {
|
||
DBG_INFO("[FS] volume reformatted OK");
|
||
} else {
|
||
DBG_ERROR("[FS] volume repair failed (fr=%d)", (int)fr);
|
||
syslog_post(SYSLOG_SELFTEST, "storage fault");
|
||
}
|
||
} else {
|
||
/* 其余错误(写保护/卷锁/参数类)不重格式化——只上报,
|
||
* 防对写锁卷整卷擦除(FTL 磨损最大操作) */
|
||
DBG_ERROR("[FS] volume probe failed (fr=%d)", (int)hfr);
|
||
syslog_post(SYSLOG_SELFTEST, "storage fault");
|
||
fr = FR_INT_ERR;
|
||
}
|
||
}
|
||
#undef FS_VOL_PROBE
|
||
if (fr == FR_OK) {
|
||
DBG_INFO("[FS] FATFS mounted");
|
||
/* 日志引擎挂载后处理:建 /log 目录 + 写"上电/重启"分类记录(唯一写者) */
|
||
syslog_mcu_mounted();
|
||
/* 加载网络配置进 RAM 副本(配置引擎属主 = 本任务;首启含旧档迁移;
|
||
* 文件缺失/损坏 = 工厂默认,不阻塞组网)。须在置位 STORAGE_READY 前,
|
||
* 使"存储就绪"屏障蕴含"配置已就绪",netTask 组网时配置恒有效 */
|
||
(void)net_cfg_load();
|
||
/* 广播"存储就绪"(= 卷挂载 + 配置加载完成),唤醒依赖文件系统的任务 */
|
||
if (sys_evtHandle != NULL) {
|
||
osEventFlagsSet(sys_evtHandle, SYS_EVT_STORAGE_READY);
|
||
}
|
||
}
|
||
} else {
|
||
DBG_ERROR("[FS] f_mount failed (fr=%d)", (int)fr);
|
||
}
|
||
|
||
return fr;
|
||
}
|
||
#endif /* !TEST_SUITE_STORAGE */
|
||
|
||
/*
|
||
* "保存网络配置"请求通知(注册给 net_cfg 的保存交接):
|
||
* uiTask 投递保存请求 → 置 SYS_EVT_CFG_SAVE → defaultTask 循环立即唤醒执行写盘
|
||
*/
|
||
#if !defined(TEST_SUITE_STORAGE)
|
||
static void default_task_net_cfg_save_notify(void)
|
||
{
|
||
if (sys_evtHandle != NULL) {
|
||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_CFG_SAVE);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/*
|
||
* 栈溢出钩子(configCHECK_FOR_STACK_OVERFLOW=2,2026-09-20 存储套件死机事故后加):
|
||
* 溢出 = 内核状态已不可信,禁止走 printf/内核对象,裸 UART 报任务名后复位。
|
||
*/
|
||
void vApplicationStackOverflowHook(void *xTask, signed char *pcTaskName)
|
||
{
|
||
static const char msg_head[] = "\r\n[ERR] STACK OVERFLOW task=";
|
||
|
||
(void)xTask;
|
||
HAL_UART_Transmit(&huart1, (const uint8_t *)msg_head,
|
||
(uint16_t)(sizeof(msg_head) - 1u), 0xFFFFu);
|
||
if (pcTaskName != NULL) {
|
||
uint16_t len = 0;
|
||
while (len < 32u && pcTaskName[len] != '\0') {
|
||
len++;
|
||
}
|
||
HAL_UART_Transmit(&huart1, (const uint8_t *)pcTaskName, len, 0xFFFFu);
|
||
}
|
||
HAL_UART_Transmit(&huart1, (const uint8_t *)"\r\n", 2u, 0xFFFFu);
|
||
HAL_Delay(200);
|
||
NVIC_SystemReset();
|
||
}
|
||
|
||
/*
|
||
* 堆分配失败钩子:只告警不复位(NULL 由各调用点自行处理并上报自检)。
|
||
* 同样走裸 UART——钩子可能在临界区内被调,printf/信号量不可用。
|
||
*/
|
||
void vApplicationMallocFailedHook(void)
|
||
{
|
||
static const char msg[] = "\r\n[ERR] MALLOC FAILED (heap exhausted)\r\n";
|
||
|
||
HAL_UART_Transmit(&huart1, (const uint8_t *)msg,
|
||
(uint16_t)(sizeof(msg) - 1u), 0xFFFFu);
|
||
}
|
||
/* USER CODE END Application */
|