E-31修复:dhara resume失败按错误分类(仅真空白判新卷),mount失败自检上报+主循环有界重试
This commit is contained in:
+170
-111
@@ -56,6 +56,10 @@
|
||||
* 之后每周期只判定"是否出现更深峰值",无新峰值不打印(保持串口静默窗口) */
|
||||
#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 -------------------------------------------------------------*/
|
||||
@@ -128,6 +132,7 @@ 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 */
|
||||
|
||||
@@ -221,126 +226,36 @@ void StartDefaultTask(void *argument)
|
||||
* f_mount(forced=1) 会触发 disk_initialize() → nand_ftl_init()
|
||||
* (dhara_map_resume)。此前默认构建从未挂载,导致 FTP 的
|
||||
* LIST/STOR/RETR 全部返回 550(f_opendir 失败)。
|
||||
* 若卷为空(FR_NO_FILESYSTEM,如恢复/坏块重建后清空),则首次自动
|
||||
* 格式化建卷(MKFS_PARM 与存储测试一致:FM_FAT32 / 无分区表)。
|
||||
* 完整 bring-up 序列(挂载 + 空卷自动建卷 + 健康探针 + 自愈重格式化
|
||||
* + 日志挂载 + 配置加载 + 就绪屏障)实现于 storage_bring_up()。
|
||||
*
|
||||
* 卷健康探针 + 自愈(2026-09-20,台架实测 /log 每次开机写失败 ×5 的
|
||||
* 根因处置):真根因 = s_append 打开模式缺 FA_WRITE 位——本 FatFs 移植
|
||||
* FA_OPEN_APPEND(0x30) = FA_OPEN_ALWAYS|FA_SEEKEND,不含 FA_WRITE(0x02),
|
||||
* 未带写位打开的文件 f_write 恒返回 FR_DENIED(7) → 打开模式已改
|
||||
* FA_WRITE|FA_OPEN_APPEND(见 sys_log_mcu.c)。残余风险 = f_mount 仅校验
|
||||
* 首扇区 BPB,FAT 区陈旧/擦除(旧镜像时代遗留、换芯片、格式化中途掉电)
|
||||
* 时挂载仍"成功",文件操作才报 FR_DENIED/FR_DISK_ERR/FR_INT_ERR 等结构
|
||||
* 性错误(注意本移植 f_write 簇分配失败返回 FR_OK+短写,探针须校验写回
|
||||
* 字节数,短写按 FR_DENIED 归一化)。挂载后做一次小写探针(两次确认,
|
||||
* 防 FTL 瞬态抖动误触发);结构性错误 → 自动重格式化自愈。代价:卷被清空
|
||||
* (/config.ini 丢失 → 配置引擎首启路径回落工厂默认并自动重建模板)——
|
||||
* 仅当卷已不可用时触发,可用设备 > 一份配置文件。 */
|
||||
* 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 = f_mount(&default_fatfs, "", 1);
|
||||
if (fr == FR_NO_FILESYSTEM) {
|
||||
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(&default_fatfs, "", 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(&default_fatfs, "", 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);
|
||||
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)投递 → 置事件 → 本任务立即执行写盘
|
||||
@@ -367,6 +282,23 @@ void StartDefaultTask(void *argument)
|
||||
/* 配置文件变更轮询(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
|
||||
}
|
||||
@@ -561,6 +493,133 @@ static void stack_probe_poll(void)
|
||||
(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 循环立即唤醒执行写盘
|
||||
|
||||
Reference in New Issue
Block a user