Files
wandering1 f754d59218 docs(测试说明): 按用途分六个子目录并同步全库引用
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/,
顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。

- 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变)
- 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts
- 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob
  (分层后不递归扫描=追溯门禁直接瞎掉)
- scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError,
  让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把
  stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法
- docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref
  搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链
  不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原)
- docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的
  「启动日志原子性复验流程」一行
- .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新,
  该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线
  (实测重建后仅 watchdog-module 一条哈希变化)
- 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与
  .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行),
  任务表块与钩子声明一字未动

验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿;
check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增
CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
2026-10-30 07:16:07 +08:00

251 lines
12 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:Task Watchdog(IWDG + 任务心跳位图)
* 模块功能:独立看门狗与"全部任务存活"绑定的对外接口
* 适用平台:STM32F407ZGTx + FreeRTOS(CMSIS-RTOS V2)
* 作者:王建锋
* 创建日期:2026-09-29
* 修改记录:
* v1.0 2026-09-29 王建锋 创建
*
* 设计依据:docs/子系统设计/看门狗设计说明.md
*/
#ifndef TASK_WATCHDOG_H
#define TASK_WATCHDOG_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* 任务 ID(位序号 = 心跳位图上的位置,与必需掩码一一对应)
* 顺序一旦确定不要插空/改动,掩码是编译期常量
*/
typedef enum {
TASK_WD_DEFAULT = 0,
TASK_WD_NET = 1,
TASK_WD_FTP = 2,
TASK_WD_TEST = 3,
TASK_WD_UI = 4,
TASK_WD_TIMESYNC = 5,
TASK_WD_ADC = 6, /* adcTask:1ms 采样节拍消费者(B1,2026-10-10) */
TASK_WD_IEC104 = 7, /* iec104Task:104 上行服务(B4,2026-10-12) */
TASK_WD_PROTECT = 8, /* protectTask:保护判据节拍(B5,2026-10-12) */
TASK_WD_ID_MAX
} task_wd_id_t;
/*
* 函数功能:任务心跳置位(各任务在自己主循环里调用)
* 入口参数:id - 任务 ID
* 返回值:无
* 限定条件:任意上下文可调(单字原子写、无内核对象依赖)
* 函数说明:仅 1~2 条指令,无锁、无阻塞,可放在中断外任意位置。
*/
void task_wd_beat(task_wd_id_t id);
/*
* 函数功能:把当前任务句柄登记进 FTL 心跳路由表(任务入口调用一次)
* 入口参数:id - 任务 ID
* 返回值:无
* 限定条件:任务上下文调用(内核启动后)
* 函数说明:nand_ftl_wd_beat() 强符号(本模块)按此表把 FTL 长耗时操作
* 的心跳路由到调用者任务的心跳位(见 gd5f2gq5ue.h 钩子说明)。
* testTask 刻意不登记:它不在必需掩码内(抽样为精确匹配),
* 其上下文置位会引入多余位反而误杀。
*/
void task_wd_bind(task_wd_id_t id);
/*
* 函数功能:启动 IWDG(**延后启动**)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 在存储 bring-up 完成后调用一次
* 函数说明:MX_IWDG_Init() 只填参数不启动(原因见 Src/iwdg.c 的
* IWDG_DEFER_START 注释:HAL_IWDG_Init 内部会立即启动,而它被
* CubeMX 插在 app_main_init 之前,那段无喂狗点且耗时可达数秒)。
* 本函数才真正启动,使"启动段不喂狗、主循环段才喂"成立。
*/
void task_wd_start(void);
/*
* 函数功能:心跳抽样与喂狗判定
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 主循环调用(唯一喂狗者)
* 函数说明:内部节流至 100ms 一次。全员到位才喂狗;连续 800ms 有任务
* 缺席则停止喂狗,由 IWDG 自然溢出复位(保留 IWDGRSTF 权威归因)。
*/
void task_wd_poll(void);
/* 观测接口(自检 / 串口报告用) */
uint32_t task_wd_last_mask(void); /* 上次抽样到的位图(0 位 = 缺席任务) */
uint32_t task_wd_miss_count(void); /* 累计未达标轮数 */
uint32_t task_wd_is_armed(void); /* 1 = IWDG 已启动 */
/*
* ==================== 第 8 轮取证:复位不丢的"进段打点" ====================
* 背景(2026-10-02 台架第 7~8 轮取证,完整链条见
* docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录):误复位时判定分支(STALL)从未
* 执行,当时判为**喂狗点 task_wd_poll 自身停摆**(第 8b 轮已更正:真因是 IWDG 超时
* 被静默配成复位默认值),而非某个被监视任务饿死。RAM 态随复位消失、串口又可能来不及打,
* 故需要一条**复位后仍可读**的"defaultTask 主循环最后走到哪一段"轨迹。
*
* 实现:轨迹槽放在 .noinit(scatter 的 UNINIT 执行区,见
* MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct 的 UNINIT_CCM)——IWDG/引脚复位
* 不改变 SRAM 内容,故复位后仍能读出上一轮的最后进段。槽布局:
* [0]=magic(校验是否为本模块写过)[1]=段号 [2]=进段时刻(ms) [3]=循环序号
* 第 8b 轮追加"时间源存活性"槽(同样在 .noinit):
* cur = [magic, uwTick, 本轮 TIM7 计数, 0](TIM7 ISR 每 1ms 更新)
* prev = [magic, uwTick, 上一轮 TIM7 计数, 0](main() 在 HAL_Init 之前快照)
* 注意:**仅 MCU 构建存在**(TASK_WATCHDOG_UNIT_TEST 下不编入,主机单测不受影响)。
* ==========================================================================
*/
/* defaultTask 主循环"进段"编号(task_wd_seg 的入参;复位后从 PREVRUN 行读出) */
typedef enum {
TASK_WD_SEG_IDLE = 0, /* 未进入任何段(初值) */
TASK_WD_SEG_POLL = 1, /* task_wd_poll(喂狗判定) */
TASK_WD_SEG_EVTWAIT = 2, /* osEventFlagsWait(50ms 兜底等待) */
TASK_WD_SEG_DRAIN = 3, /* dbg_console_drain(控制台排空) */
TASK_WD_SEG_CFGSAVE = 4, /* net_cfg_save_run(配置保存) */
TASK_WD_SEG_CFGPOLL = 5, /* cfg_file_poll(2s 配置轮询) */
TASK_WD_SEG_LED = 6, /* LED 心跳 */
TASK_WD_SEG_SYSLOG = 7, /* syslog_drain(运行期日志落盘) */
TASK_WD_SEG_STACK = 8 /* stack_probe_poll(栈水位观测) */
} task_wd_seg_t;
/*
* 函数功能:记录"即将进入的主循环段"(第 8 轮取证;**仅 MCU 构建**)
* 入口参数:seg - 段号(task_wd_seg_t)
* 返回值:无
* 限定条件:defaultTask 主循环调用(任意上下文亦可,纯 SRAM 写)
* 函数说明:4 字写(magic/段/时刻/序号),进 .noinit 槽;复位不丢。
*/
void task_wd_seg(uint32_t seg);
/*
* 函数功能:开机上报上一轮最后进段 + 时间源存活性(第 8 轮取证;**仅 MCU 构建**)
* 入口参数:无
* 返回值:无
* 限定条件:启动早期调用(内核启动前亦可,裸 UART 直发)
* 函数说明:magic 命中才打一行 `PREVRUN seg=<段> seq=<序号> t=<ms>`(裸 UART,
* 不依赖环形缓冲写者),随后清 magic(一次复位只报一次)。
* 第 8b 轮:紧接着打一行 `TICKRUN uw= n= duw=`——uw 为 TIM7 在**上一轮**
* 最后一次记下的 HAL tick、n 为上一轮 TIM7 中断总数、duw = uw - t
* (相对进段时刻的增量),据此判定 TIM7 是否在冻结同刻停摆。
*/
void task_wd_seg_report(void);
/*
* 函数功能:开机快照上一轮的"时间源存活性"(第 8b 轮取证;**仅 MCU 构建**)
* 入口参数:无
* 返回值:无
* 限定条件:**必须在 HAL_Init() 之前调用**(main() 的 USER CODE BEGIN 1)
* 函数说明:TIM7 在 HAL_Init 里即开始 1ms 中断,而开机上报要到 app_main_init
* (≈117ms)才跑;本函数把上一轮的终值先搬到 prev 槽再清空 cur,
* 否则终值会被本轮开机瞬间的 tick 覆盖(2026-10-02 台架实测踩坑)。
*/
void task_wd_tick_snapshot(void);
/*
* 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(第 8b 轮取证;**仅 MCU 构建**)
* 入口参数:无
* 返回值:无
* 限定条件:TIM7 中断上下文调用(`HAL_TIM_PeriodElapsedCallback` 内,每 1ms)
* 函数说明:更新 cur 槽 = [magic, uwTick, 本轮 TIM7 中断计数, 0](纯 SRAM 写,无阻塞)。
* 与 task_wd_seg 的"进段打点"配合,复位后由 task_wd_seg_report() 上报。
* 背景(2026-10-02 台架第 8b 轮取证,见 docs/子系统设计/看门狗设计说明.md
* 2026-10-02 变更记录):HAL_UART_Transmit 的超时判据
* 依赖 uwTick,时间源停摆即死等——必须先把"TIM7 是否停摆"钉死再定向修复。
* ⚠ **本函数内严禁调用任何 FreeRTOS/CMSIS-RTOS API**:TIM7 在 HAL_Init
* 阶段即开始中断(远早于 xPortStartScheduler),此时 ISR 版内核 API 会触发
* port.c 的优先级断言并把系统卡死在 TIM7 ISR 内(2026-10-02 台架实测根因)。
*/
void task_wd_tick_probe(void);
/*
* ============================ 平台后端(内部) ============================
* 把判定状态机中的硬件访问收敛到三个薄钩子,使"取走清零 / 掩码判定 / 缺失
* 累计 / 节流"成为纯逻辑,可在主机侧用桩替换后离线穷举验证
* (见 test/host/watchdog/ 与 docs/测试说明/单元测试/看门狗单元测试说明.md)。
*
* 与 App/cfg/cfg_file_store.h 的"共享核心 + 平台后端"同款做法:
* - 三者由 task_watchdog.c 以 __weak 提供 MCU 默认实现(读写 IWDG 寄存器 /
* 调 HAL_GetTick),**行为与不抽钩子时逐字一致**;
* - 主机侧单测提供同名**强符号**覆盖 → 不碰 MCU 代码即可观测/注入;
* - MCU 构建零行为变化。
*
* ⚠ 非模块对外接口,业务代码**不要**直接调用(由 task_wd_* 间接使用)。
* ========================================================================
*/
/*
* 函数功能:重装 IWDG 计数器(喂狗)
* 返回值:无
* 函数说明:MCU 默认实现 = `IWDG->KR = 0x0000AAAAu;`
* 主机侧单测覆盖此符号以记录"何时被喂"。
*/
void wd_platform_feed(void);
/*
* 函数功能:按设计参数配置并启动 IWDG(不可逆,启动后只能靠复位停止)
* 返回值:无
* 函数说明:MCU 默认实现 = 解锁 PR/RLR → 写 WD_IWDG_PRESCALER/WD_IWDG_RELOAD
* (/64 + 1249 → 1701~2498ms,等 SR.PVU/RVU 同步)→ `IWDG->KR = 0xCCCC`。
* ⚠ 配置**必须**在此完成:`Src/iwdg.c` 为实现延后启动,把生成的
* `HAL_IWDG_Init(&hiwdg)` 经宏改名成空实现(见该文件 `USER CODE BEGIN 0`),
* 而 `HAL_IWDG_Init()` 是 HAL 里唯一把 `hiwdg.Init.*` 写进寄存器的路径;
* 缺了这一步 PR/RLR 停在复位默认值(PR=0/RLR=4095 ≈ 400ms),
* 超时比设计短约 5 倍 → 800ms 容忍分支成死代码、易 IWDG 误复位。
* 主机侧单测以强符号覆盖本钩子(不碰寄存器),故其行为不受影响。
*/
void wd_platform_start(void);
/*
* 函数功能:读单调毫秒时基(用于 100ms 抽样节流)
* 返回值:自启动以来的毫秒计数(32 位回绕安全,用无符号差比较)
* 函数说明:MCU 默认实现 = `HAL_GetTick()`
* 主机侧单测覆盖此符号以驱动时间推进。
*/
uint32_t wd_platform_tick_ms(void);
/*
* 函数功能:lftpd 看门狗心跳钩子(**本模块提供强符号实现**)
* 入口参数:无
* 返回值:无
* 限定条件:任意上下文(IWDG 启动前只置位图,不影响判定)
* 函数说明:驱动层(lftpd.c / lftpd_inet.c 的节拍点)只调用弱钩子,本模块以
* 强符号覆盖并路由到 TASK_WD_FTP 心跳位——驱动层不向上依赖应用层
* (分层铁律)。声明于 lftpd.h 供驱动侧引用,此处为"本模块提供"侧的声明。
*/
void lftpd_task_wd_beat(void);
/*
* 函数功能:NAND FTL 直接喂狗钩子(**本模块提供强符号实现**)
* 入口参数:无
* 返回值:无
* 限定条件:任意上下文(IWDG 未启动时为空操作)
* 函数说明:FTL 长耗时路径(GC/块擦除等待)在调用者任务上下文**直接写 IWDG->KR**,
* 覆盖"喂狗点(defaultTask 主循环)自身被长 I/O 阻塞"的误复位形态。
* 声明于 gd5f2gq5ue.h 供驱动侧引用,此处为"本模块提供"侧的声明。
*/
void nand_ftl_wd_feed(void);
/*
* 函数功能:复位判定状态机(**仅主机侧单元测试使用**)
* 返回值:无
* 函数说明:仅当定义 TASK_WATCHDOG_UNIT_TEST 时编入(MCU 构建无此符号)。
* 用于在用例之间清空位图/计数/节流基线,保证用例互不污染。
*/
#ifdef TASK_WATCHDOG_UNIT_TEST
void task_wd_test_reset(void);
#endif
#ifdef __cplusplus
}
#endif
#endif /* TASK_WATCHDOG_H */