CCM扩容FreeRTOS堆+DMA阈值1024防栈缓冲进CCM+HardFault现场报告与栈/内存钩子+修defaultTask栈溢出+LVGL绘制缓冲护栏+存储套件05/06与恢复模式
This commit is contained in:
@@ -48,14 +48,14 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
|
||||
|
||||
| Task | 栈 (words) | 优先级 |
|
||||
|---|---|---|
|
||||
| defaultTask | 512 | Normal |
|
||||
| defaultTask | 1024 | Normal |
|
||||
| netTask | 1024 | Normal |
|
||||
| ftpTask | 1024 | Normal |
|
||||
| testTask | 1024 | BelowNormal |
|
||||
| testTask | 2048 | BelowNormal |
|
||||
| uiTask | 4096 | BelowNormal |
|
||||
| timeSyncTask | 512 | Normal |
|
||||
|
||||
(实际值以 `Src/freertos.c` 为准,六任务栈合计 32KB;改动栈/缓冲前先核对此表与实测)
|
||||
(实际值以 `Src/freertos.c` 为准;testTask 仅在启用测试套件时创建。改动栈/缓冲前先核对此表与实测。2026-09-21 上调:defaultTask 512→1024(扛 mount/mkfs/FAT探针/cfg/syslog 深层 FatFS I/O,旧值经 `configCHECK_FOR_STACK_OVERFLOW=2` 钩子实测溢出)、testTask 1024→2048(存储套件 deep call chain + 局部缓冲溢出)。FreeRTOS 堆已迁 CCM,任务栈合计已不占 SRAM)
|
||||
|
||||
## 架构要点
|
||||
|
||||
|
||||
@@ -46,7 +46,12 @@ extern SPI_HandleTypeDef hspi1;
|
||||
volatile uint8_t g_spi1_dma_done;
|
||||
|
||||
/* DMA 阈值(字节数超过此值使用 DMA 传输) */
|
||||
#define GD5F_DMA_THRESHOLD 32
|
||||
/* DMA 阈值 1024B(2026-09-20):阈值以下走 CPU 轮询。存在把**栈上小缓冲**
|
||||
* 直接传进本驱动做收发的大小(dhara meta 读 132B、dhara_nand_is_free 64B),
|
||||
* 任务栈随 FreeRTOS 堆驻留 CCM(0x10000000),而 DMA1/DMA2 物理上不可达
|
||||
* CCM——此类传输必须留在轮询域,否则静默失败(CCM 首次扩容实验实证的根因)。
|
||||
* 页级大块传输(2048)仍走 DMA,源/目的全部为文件作用域静态缓冲。 */
|
||||
#define GD5F_DMA_THRESHOLD 1024
|
||||
|
||||
/*
|
||||
* 函数功能:SPI1 TX DMA 完成回调(PROGRAM_LOAD 数据发送用)
|
||||
|
||||
@@ -14,8 +14,16 @@
|
||||
|
||||
/* 160x160 1bpp 帧 = 3200 字节 */
|
||||
static uint8_t g_ui_fbuf[UI_FB_SIZE];
|
||||
/* LVGL 绘制缓冲(与帧同尺寸,整屏渲染) */
|
||||
static uint8_t g_ui_draw_buf[UI_FB_SIZE];
|
||||
|
||||
/* LVGL 绘制缓冲(整屏渲染)。LVGL 9.3.0 对 I1 格式的整帧渲染,会在其自身算出的
|
||||
* stride*h(=3200) 之后再越界多写 1 个字节(实测台架金丝雀命中 draw_buf[3200]=0xFF)。
|
||||
* 若紧邻其后的 .bss(本例 ui_menu_view.o 的 s_frame_box)恰有指针,其最低字节被
|
||||
* 打成 0xFF → 野指针 → HardFault(PC=lv_obj_get_scroll_y,2026-09-20 存储套件实证,
|
||||
* SRAM/CCM 两种堆布局下同一签名、与 CCM 无关)。物理多留 UI_DRAW_BUF_GUARD 字节
|
||||
* 吸收该越界写;逻辑尺寸仍按 UI_FB_SIZE 上报 LVGL(渲染数学 stride*h 不变)。
|
||||
* Lib/LVGL 为第三方库,不改其源码,越界容纳放在本移植层。 */
|
||||
#define UI_DRAW_BUF_GUARD 32
|
||||
static uint8_t g_ui_draw_buf[UI_FB_SIZE + UI_DRAW_BUF_GUARD];
|
||||
|
||||
/* 单键缓冲:菜单无需多键队列,仅保存最近一次待处理键 */
|
||||
static volatile uint8_t g_pending_key = 0;
|
||||
|
||||
@@ -77,6 +77,15 @@
|
||||
#define configUSE_RECURSIVE_MUTEXES 1
|
||||
#define configUSE_COUNTING_SEMAPHORES 1
|
||||
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
|
||||
/* USER CODE BEGIN FREE_RTOS_CONFIG_HARDENING */
|
||||
/* 栈溢出机械检测(2026-09-20 存储套件死机事故后加):模式 2 在每次上下文切换
|
||||
* 检查栈底水位线,溢出即调 vApplicationStackOverflowHook(报任务名+复位)。
|
||||
* 关闭检测时溢出会静默腐蚀堆相邻块(其它任务栈/TCB),表现为无异常、不可定位
|
||||
* 的全系统冻结——本次事故签名。开销:每次切换一次字比较,可忽略。 */
|
||||
#define configCHECK_FOR_STACK_OVERFLOW 2
|
||||
/* pvPortMalloc 失败钩子(只告警不复位:分配失败路径由各调用点 NULL 检查处理) */
|
||||
#define configUSE_MALLOC_FAILED_HOOK 1
|
||||
/* USER CODE END FREE_RTOS_CONFIG_HARDENING */
|
||||
/* USER CODE BEGIN MESSAGE_BUFFER_LENGTH_TYPE */
|
||||
/* Defaults to size_t for backward compatibility, but can be changed
|
||||
if lengths will always be less than the number of bytes in a size_t. */
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
; *************************************************************
|
||||
; *** Scatter-Loading Description File (manual) ***
|
||||
; *** IRAM1 = SRAM1+SRAM2 128KB continuous RAM. ***
|
||||
; *** No SRAM3 usage (may not exist on clone MCUs). ***
|
||||
; *** IRAM1 = SRAM1+SRAM2 128KB continuous RAM (DMA-reachable)***
|
||||
; *** CCM = 64KB Core-Coupled Memory @0x10000000 (SRAM3) ***
|
||||
; *** CCM 直连 M4 内核零等待,但 DMA1/DMA2 物理不可达! ***
|
||||
; *** 本工程只把 FreeRTOS 堆(heap_4.o:任务栈/队列/暂存) ***
|
||||
; *** 放入 CCM;全部 DMA 收发缓冲均为文件作用域静态(留在 ***
|
||||
; *** SRAM),且 GD5F 驱动 DMA 阈值已提到 1024B——栈上小缓 ***
|
||||
; *** 冲(dhara meta 132B / is_free 64B)走轮询不进 DMA。 ***
|
||||
; *** 两条前提缺任何一条 = 静默数据损坏(2026-09-20 首轮 ***
|
||||
; *** 扩容实验实证)。CCM 存在性本机 ST-LINK 实测可读写; ***
|
||||
; *** 克隆片可能缺 SRAM3,换料必须重测! ***
|
||||
; *************************************************************
|
||||
|
||||
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
|
||||
@@ -11,7 +19,10 @@ LR_IROM1 0x08000000 0x00100000 { ; load region size_region
|
||||
.ANY (+RO)
|
||||
.ANY (+XO)
|
||||
}
|
||||
RW_IRAM1 0x20000000 0x00020000 { ; SRAM1+SRAM2, 128KB
|
||||
RW_IRAM1 0x20000000 0x00020000 { ; SRAM1+SRAM2, 128KB (DMA-safe zone)
|
||||
.ANY (+RW +ZI)
|
||||
}
|
||||
RW_CCM 0x10000000 0x00010000 { ; CCM 64KB: FreeRTOS heap only (no DMA!)
|
||||
heap_4.o (+RW +ZI) ; ucHeap 48KB: 任务栈/队列/定时器
|
||||
}
|
||||
}
|
||||
|
||||
+61
-3
@@ -25,6 +25,7 @@
|
||||
|
||||
/* Private includes ----------------------------------------------------------*/
|
||||
/* USER CODE BEGIN Includes */
|
||||
#include "test_config.h" /* 套件选择:STORAGE 套件下 defaultTask 让出存储所有权 */
|
||||
#include <string.h>
|
||||
#include "net_task.h"
|
||||
#include "ch395f.h"
|
||||
@@ -72,7 +73,10 @@ const osThreadAttr_t timeSyncTask_attributes = {
|
||||
osThreadId_t defaultTaskHandle;
|
||||
const osThreadAttr_t defaultTask_attributes = {
|
||||
.name = "defaultTask",
|
||||
.stack_size = 512 * 4,
|
||||
.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 */
|
||||
@@ -93,7 +97,7 @@ const osThreadAttr_t ftpTask_attributes = {
|
||||
osThreadId_t testTaskHandle;
|
||||
const osThreadAttr_t testTask_attributes = {
|
||||
.name = "testTask",
|
||||
.stack_size = 1024 * 4,
|
||||
.stack_size = 2048 * 4, /* 1024→2048: 存储套件 deep call chain + 局部缓冲,4KB 实测溢出(2026-09-20) */
|
||||
.priority = (osPriority_t) osPriorityBelowNormal,
|
||||
};
|
||||
/* Definitions for uiTask */
|
||||
@@ -117,7 +121,9 @@ const osEventFlagsAttr_t sys_evt_attributes = {
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
/* USER CODE BEGIN FunctionPrototypes */
|
||||
void ui_task_func(void *argument);
|
||||
#if !defined(TEST_SUITE_STORAGE)
|
||||
static void default_task_net_cfg_save_notify(void);
|
||||
#endif
|
||||
/* USER CODE END FunctionPrototypes */
|
||||
|
||||
void StartDefaultTask(void *argument);
|
||||
@@ -225,7 +231,12 @@ void StartDefaultTask(void *argument)
|
||||
* 防 FTL 瞬态抖动误触发);结构性错误 → 自动重格式化自愈。代价:卷被清空
|
||||
* (/config.ini 丢失 → 配置引擎首启路径回落工厂默认并自动重建模板)——
|
||||
* 仅当卷已不可用时触发,可用设备 > 一份配置文件。 */
|
||||
static FATFS default_fatfs;
|
||||
#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 = f_mount(&default_fatfs, "", 1);
|
||||
if (fr == FR_NO_FILESYSTEM) {
|
||||
DBG_INFO("[FS] no FAT volume, formatting NAND...");
|
||||
@@ -330,6 +341,10 @@ void StartDefaultTask(void *argument)
|
||||
/* 注册"保存网络配置"请求通知: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;
|
||||
@@ -341,16 +356,22 @@ void StartDefaultTask(void *argument)
|
||||
(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)
|
||||
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
|
||||
}
|
||||
/* USER CODE END StartDefaultTask */
|
||||
}
|
||||
@@ -438,10 +459,47 @@ void StartUiTask(void *argument)
|
||||
* "保存网络配置"请求通知(注册给 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 */
|
||||
|
||||
+79
-9
@@ -22,6 +22,7 @@
|
||||
#include "stm32f4xx_it.h"
|
||||
/* Private includes ----------------------------------------------------------*/
|
||||
/* USER CODE BEGIN Includes */
|
||||
#include "usart.h"
|
||||
/* USER CODE END Includes */
|
||||
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -95,17 +96,86 @@ void NMI_Handler(void)
|
||||
/**
|
||||
* @brief This function handles Hard fault interrupt.
|
||||
*/
|
||||
void HardFault_Handler(void)
|
||||
/* USER CODE BEGIN HardFault_Handler */
|
||||
/* 崩溃现场采集(本函数为手工改写,CubeMX 重生成会覆盖!):
|
||||
* EXC_RETURN bit2 判断异常帧在 MSP 还是 PSP,取帧指针交给 C 报告函数。
|
||||
* 空 while(1) 版会让死机在日志上完全静默(2026-09-20 存储套件死机排查实证) */
|
||||
__asm void HardFault_Handler(void)
|
||||
{
|
||||
/* USER CODE BEGIN HardFault_IRQn 0 */
|
||||
|
||||
/* USER CODE END HardFault_IRQn 0 */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
|
||||
/* USER CODE END W1_HardFault_IRQn 0 */
|
||||
}
|
||||
extern hard_fault_report
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B hard_fault_report
|
||||
}
|
||||
/* USER CODE END HardFault_Handler */
|
||||
|
||||
/* USER CODE BEGIN HardFault_Report */
|
||||
/*
|
||||
* 函数功能:HardFault 现场报告(裸机上下文,不经内核/printf)
|
||||
* 入口参数:frame - 异常栈帧 {R0,R1,R2,R3,R12,LR,PC,xPSR}
|
||||
* 函数说明:直接 HAL_UART_Transmit 轮询输出 PC/LR/xPSR 与 CFSR/HFSR 故障
|
||||
* 寄存器,供开机日志定位崩溃点(对照 .map 找符号);输出后复位。
|
||||
*/
|
||||
static void hard_fault_puts(const char *s)
|
||||
{
|
||||
uint16_t len = 0;
|
||||
|
||||
while (s[len] != '\0') {
|
||||
len++;
|
||||
}
|
||||
HAL_UART_Transmit(&huart1, (const uint8_t *)s, len, 0xFFFFu);
|
||||
}
|
||||
|
||||
static void hard_fault_puthex(uint32_t v)
|
||||
{
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
char out[9];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
out[i] = digits[(v >> (28 - i * 4)) & 0xFu];
|
||||
}
|
||||
out[8] = '\0';
|
||||
hard_fault_puts(out);
|
||||
}
|
||||
|
||||
void hard_fault_report(uint32_t *frame)
|
||||
{
|
||||
hard_fault_puts("\r\n[ERR] HARDFAULT PC=");
|
||||
hard_fault_puthex(frame[6]);
|
||||
hard_fault_puts(" LR=");
|
||||
hard_fault_puthex(frame[5]);
|
||||
hard_fault_puts(" xPSR=");
|
||||
hard_fault_puthex(frame[7]);
|
||||
hard_fault_puts(" R0=");
|
||||
hard_fault_puthex(frame[0]);
|
||||
hard_fault_puts(" R1=");
|
||||
hard_fault_puthex(frame[1]);
|
||||
hard_fault_puts(" R2=");
|
||||
hard_fault_puthex(frame[2]);
|
||||
hard_fault_puts(" R3=");
|
||||
hard_fault_puthex(frame[3]);
|
||||
hard_fault_puts(" CFSR=");
|
||||
hard_fault_puthex(SCB->CFSR);
|
||||
hard_fault_puts(" HFSR=");
|
||||
hard_fault_puthex(SCB->HFSR);
|
||||
|
||||
/* BFAR/MMFAR 仅在对应 valid 位为 1 时有意义 */
|
||||
if ((SCB->CFSR & (1UL << 15)) != 0U) {
|
||||
hard_fault_puts(" BFAR=");
|
||||
hard_fault_puthex(SCB->BFAR);
|
||||
}
|
||||
if ((SCB->CFSR & (1UL << 7)) != 0U) {
|
||||
hard_fault_puts(" MMFAR=");
|
||||
hard_fault_puthex(SCB->MMFAR);
|
||||
}
|
||||
|
||||
HAL_Delay(200);
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
/* USER CODE END HardFault_Report */
|
||||
|
||||
/**
|
||||
* @brief This function handles Memory management fault.
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ void storage_test_run(void);
|
||||
/* 存储套件恢复测试(默认关闭):取消注释并编译 STORAGE 套件运行一次,
|
||||
* 即可重建 BBT(清除 TC-STO-03 等注入的坏块)并重新格式化,恢复设备干净态。
|
||||
* 恢复完成后建议重新注释并编译正常构建。 */
|
||||
//#define ENABLE_STORAGE_RECOVERY_TESTS
|
||||
//#define ENABLE_STORAGE_RECOVERY_TESTS /* 台架已跑过一次恢复(2026-09-20 BBT 回出厂态),保持注释 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+174
-14
@@ -19,15 +19,22 @@
|
||||
|
||||
storage_test_stats_t g_storage_stats;
|
||||
|
||||
static uint8_t s_perf_buf[4096];
|
||||
#ifndef ENABLE_STORAGE_RECOVERY_TESTS
|
||||
/* 全套件共享的 FATFS/FIL(RAM 预算紧张:每个 static FATFS≈560B、FIL≈600B,
|
||||
* 各测试函数独立持有会撑爆 .bss。测试在单任务 testTask 内串行执行、从不并发,
|
||||
* 共用一组安全,且比"每函数一份"省下约 5KB。见产品级 RAM 溢出 L6406E) */
|
||||
static FATFS fs;
|
||||
static FIL fil;
|
||||
|
||||
/* 性能缓冲 2048B = 恰好一个 NAND 页(RAM 预算:4096 时套件链接溢出 0x604,
|
||||
* L6406E 实测;2048 分块仍覆盖整页写路径,128KB 总量不变) */
|
||||
static uint8_t s_perf_buf[2048];
|
||||
|
||||
/* TC-STO-01: FTL 掉电恢复(journal 持久化)
|
||||
* 写文件并同步落盘,随后模拟掉电(卸载 + 复位 FTL + 重新 resume),再挂载校验内容一致。
|
||||
*/
|
||||
static void storage_test_powerloss(void)
|
||||
{
|
||||
static FATFS fs;
|
||||
static FIL fil;
|
||||
const uint32_t len = 4096;
|
||||
uint8_t wbuf[512];
|
||||
uint8_t rbuf[512];
|
||||
@@ -90,14 +97,14 @@ static void storage_test_powerloss(void)
|
||||
|
||||
/* TC-STO-02: 大文件 / 多扇区 / 随机 seek
|
||||
* 写 256KB 位置相关模式文件,顺序读回比对 + 随机偏移 seek 读比对。
|
||||
* 栈预算:testTask 栈 4KB,局部缓冲 4096 必然溢出栈(8 月侥幸未爆是堆使用
|
||||
* 低),改 2048 = 恰好一个 NAND 整页,覆盖度等价。
|
||||
*/
|
||||
static void storage_test_largefile(void)
|
||||
{
|
||||
static FATFS fs;
|
||||
static FIL fil;
|
||||
const uint32_t total = 256 * 1024;
|
||||
const uint32_t chunk = 4096;
|
||||
uint8_t buf[4096];
|
||||
const uint32_t chunk = 2048;
|
||||
uint8_t buf[2048];
|
||||
UINT bw, br;
|
||||
uint32_t off, i;
|
||||
int ok = 1;
|
||||
@@ -162,8 +169,6 @@ static void storage_test_largefile(void)
|
||||
*/
|
||||
static void storage_test_badblock(void)
|
||||
{
|
||||
static FATFS fs;
|
||||
static FIL fil;
|
||||
uint8_t wbuf[512], rbuf[512];
|
||||
UINT bw, br;
|
||||
uint32_t bad[3];
|
||||
@@ -242,17 +247,150 @@ static void storage_test_badblock(void)
|
||||
STORAGE_TEST_CHECK(badok, "TC-STO-03 injected blocks still reported bad");
|
||||
|
||||
f_unlink("bbt.dat");
|
||||
/* 注意:注入的坏块已持久化,测试设备专用;如需恢复需重建 BBT / 重新格式化 */
|
||||
/* 注意:注入的坏块已持久化,测试设备专用;如需恢复需重建 BBT / 重新格式化
|
||||
* (配套 TC-STO-04 恢复测试:storage_test.h 启用 ENABLE_STORAGE_RECOVERY_TESTS
|
||||
* 后跑一次套件,即可清除运行时注入坏块 + 清 dhara map 回出厂态) */
|
||||
}
|
||||
|
||||
/* TC-STO-05: 文件名/路径合法性校验(守护 ".fschk 无限重格式化" 真根因)
|
||||
* 本移植 FF_USE_LFN=0,只支持严格 8.3 短名(主名≤8、扩展≤3、不以点开头)。
|
||||
* 非法名字 f_open 必返 FR_INVALID_NAME(纯 RAM 名字判定、不碰盘);合法但
|
||||
* 不存在的名字返 FR_NO_FILE。本测试断言两类严格可区分——一旦哪天有人把
|
||||
* FR_INVALID_NAME 误归入"卷损坏"自愈集合,这条测试就是最后的机械防线。
|
||||
* (详见 docs/问题记录/GD5F2GQ5UE_Trap_Records.md Trap 05 / AGENTS.md)
|
||||
*/
|
||||
static void storage_test_name_validation(void)
|
||||
{
|
||||
uint32_t i;
|
||||
int gok = 1;
|
||||
int bok = 1;
|
||||
|
||||
/* 合法 8.3:FA_READ 打开 → FR_OK(存在) 或 FR_NO_FILE(不存在),均非 INVALID */
|
||||
static const char *good[] = {
|
||||
"fschk.tmp", /* 修复后的探针名(名5+扩展3) */
|
||||
"config.ini", /* 名6+扩展3 */
|
||||
"12345678.txt", /* 主名 8 / 扩展 3 上限 */
|
||||
"A.TXT", /* 最小合法名 */
|
||||
"NOEXT", /* 无扩展 */
|
||||
"1.23", /* 数字名 */
|
||||
"run.log"
|
||||
};
|
||||
/* 非法:FF_USE_LFN=0 下 create_short 必拒 → FR_INVALID_NAME */
|
||||
static const char *bad[] = {
|
||||
".fschk", /* 事故根因:点开头(点后当扩展域,超 3 上限) */
|
||||
".hidden", /* 点开头隐藏名风格 */
|
||||
"123456789", /* 主名 9 > 8 */
|
||||
"toolongname.txt", /* 主名 > 8 */
|
||||
"a.toolongext", /* 扩展 > 3 */
|
||||
"bad?char.txt", /* 非法字符 ? */
|
||||
"star*name", /* * */
|
||||
"col:on", /* : */
|
||||
"lt<gt>val", /* < > */
|
||||
"pipe|name" /* | */
|
||||
};
|
||||
|
||||
DBG_INFO("=== Storage Test: TC-STO-05 Filename validation ===");
|
||||
|
||||
if (f_mount(&fs, "", 1) != FR_OK) {
|
||||
STORAGE_TEST_CHECK(0, "TC-STO-05 mount");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < (uint32_t)(sizeof(good) / sizeof(good[0])); i++) {
|
||||
FRESULT r = f_open(&fil, good[i], FA_READ);
|
||||
if (r == FR_OK) {
|
||||
f_close(&fil);
|
||||
}
|
||||
if (r == FR_INVALID_NAME) {
|
||||
gok = 0;
|
||||
DBG_ERROR(" valid name wrongly rejected: '%s'", good[i]);
|
||||
}
|
||||
}
|
||||
STORAGE_TEST_CHECK(gok, "TC-STO-05 valid 8.3 names accepted");
|
||||
|
||||
for (i = 0; i < (uint32_t)(sizeof(bad) / sizeof(bad[0])); i++) {
|
||||
FRESULT r = f_open(&fil, bad[i], FA_READ);
|
||||
if (r == FR_OK) {
|
||||
f_close(&fil);
|
||||
}
|
||||
/* 含 ':' 的名字被 FatFs 先解析为盘符前缀 → FR_INVALID_DRIVE(11)
|
||||
* 同样属合法拒绝(台架实测 'col:on' fr=11),与 INVALID 同等待遇 */
|
||||
if ((r != FR_INVALID_NAME) &&
|
||||
!((r == FR_INVALID_DRIVE) && (strchr(bad[i], ':') != NULL))) {
|
||||
bok = 0;
|
||||
DBG_ERROR(" invalid name NOT rejected: '%s' -> fr=%d", bad[i], (int)r);
|
||||
}
|
||||
}
|
||||
STORAGE_TEST_CHECK(bok, "TC-STO-05 invalid names rejected (FR_INVALID_NAME)");
|
||||
}
|
||||
|
||||
/* TC-STO-06: 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱)
|
||||
* 本移植 FA_OPEN_APPEND 不含 FA_WRITE → 不带写位打开 f_write 必返 FR_DENIED;
|
||||
* 追加写必须 FA_WRITE|FA_OPEN_APPEND。另 f_write 簇耗尽返 FR_OK+短写(无错误
|
||||
* 码),判成败必须校验写回字节数。锁定这两条移植专有语义防回归。
|
||||
*/
|
||||
static void storage_test_open_modes(void)
|
||||
{
|
||||
UINT bw;
|
||||
FRESULT r;
|
||||
|
||||
DBG_INFO("=== Storage Test: TC-STO-06 Open-mode semantics ===");
|
||||
|
||||
if (f_mount(&fs, "", 1) != FR_OK) {
|
||||
STORAGE_TEST_CHECK(0, "TC-STO-06 mount");
|
||||
return;
|
||||
}
|
||||
(void)f_unlink("mode.tmp");
|
||||
|
||||
/* 1) 建一个含初始内容的文件 */
|
||||
r = f_open(&fil, "mode.tmp", FA_CREATE_ALWAYS | FA_WRITE);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STO-06 create base file");
|
||||
if (r == FR_OK) {
|
||||
f_write(&fil, "abc", 3, &bw);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
/* 2) FA_OPEN_APPEND(无 FA_WRITE)→ 写必返 FR_DENIED */
|
||||
r = f_open(&fil, "mode.tmp", FA_OPEN_APPEND);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STO-06 open append(no write)");
|
||||
if (r == FR_OK) {
|
||||
FRESULT wr = f_write(&fil, "xyz", 3, &bw);
|
||||
STORAGE_TEST_CHECK(wr == FR_DENIED,
|
||||
"TC-STO-06 append-no-write must DENY (fr=%d)", (int)wr);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
/* 3) FA_WRITE|FA_OPEN_APPEND → 追加成功且字节数正确 */
|
||||
r = f_open(&fil, "mode.tmp", FA_WRITE | FA_OPEN_APPEND);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STO-06 open write|append");
|
||||
if (r == FR_OK) {
|
||||
(void)f_write(&fil, "xyz", 3, &bw);
|
||||
STORAGE_TEST_CHECK(bw == 3, "TC-STO-06 append byte-count (%u)", bw);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
/* 4) 只读打开后写 → 必返 FR_DENIED(读句柄不可写) */
|
||||
r = f_open(&fil, "mode.tmp", FA_READ);
|
||||
STORAGE_TEST_CHECK(r == FR_OK, "TC-STO-06 open read-only");
|
||||
if (r == FR_OK) {
|
||||
FRESULT wr = f_write(&fil, "q", 1, &bw);
|
||||
STORAGE_TEST_CHECK(wr == FR_DENIED,
|
||||
"TC-STO-06 read-only write must DENY (fr=%d)", (int)wr);
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
(void)f_unlink("mode.tmp");
|
||||
}
|
||||
#endif /* !ENABLE_STORAGE_RECOVERY_TESTS: 共享缓冲 + 正常测试函数 */
|
||||
|
||||
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
|
||||
static void storage_test_recover(void);
|
||||
#endif
|
||||
|
||||
void storage_test_run(void)
|
||||
#ifndef ENABLE_STORAGE_RECOVERY_TESTS
|
||||
/* 正常存储测试套件主体(恢复模式见文件末尾 storage_test_run 分发) */
|
||||
static void storage_test_run_normal(void)
|
||||
{
|
||||
static FATFS fs;
|
||||
static FIL fil;
|
||||
static uint8_t work[FF_MAX_SS];
|
||||
char read_buf[64];
|
||||
const char *test_str = "Hello FatFS + dhara FTL!";
|
||||
@@ -341,7 +479,11 @@ void storage_test_run(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 存储集成测试(TC-STO-01~03) ==================== */
|
||||
/* ==================== 存储集成测试(TC-STO-05~06 契约 + 01~03 集成) ========== */
|
||||
|
||||
/* 纯语义契约测试(非破坏性,先跑):名字合法性 + 打开模式 */
|
||||
storage_test_name_validation();
|
||||
storage_test_open_modes();
|
||||
|
||||
storage_test_powerloss();
|
||||
storage_test_largefile();
|
||||
@@ -355,6 +497,24 @@ void storage_test_run(void)
|
||||
|
||||
STORAGE_TEST_REPORT("Storage Tests");
|
||||
}
|
||||
#endif /* !ENABLE_STORAGE_RECOVERY_TESTS */
|
||||
|
||||
/*
|
||||
* 套件总入口分发。恢复模式(storage_test.h 打开 ENABLE_STORAGE_RECOVERY_TESTS)
|
||||
* = 专用维护构建:重建 BBT(清除运行时误标坏块)+ 清 dhara map 后直接返回,
|
||||
* 不在受污染介质上继续跑其余测试(2026-09-20:CCM 首轮实验中 DMA 够不到栈上
|
||||
* 小缓冲,随机块被误标坏并持久化,journal 引用到即静默卡死——恢复模式即为其
|
||||
* 清账手段)。恢复完成后重新注释该宏、编译正常套件即可。
|
||||
*/
|
||||
void storage_test_run(void)
|
||||
{
|
||||
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
|
||||
storage_test_recover();
|
||||
STORAGE_TEST_REPORT("Storage Recovery");
|
||||
#else
|
||||
storage_test_run_normal();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
|
||||
/* TC-STO-04: 重建 BBT + 重新格式化(恢复 TC-STO-03 注入的坏块)
|
||||
|
||||
Reference in New Issue
Block a user