修复 P2 全部 10 项:错误传播/竞态/门控/文档失真
待办池 docs/待处理内容.md §3.2 的 10 个 P2 全部闭环;逐项落代码 + 注释说明。 P2-1 cfg_file: 受管键行超 CFG_LINE_TEST_MAX 拒绝保存(防重复键行) P2-2 ch395f: DMA 失败传播状态 + 锁存通道故障 + 读不拷不可信数据 P2-7 net_select: LISTENING 不再恒判可读(消除 select 忙轮询地雷) P2-8 time_sync: 核实 L3 pending 改写已闭环(net_recv_to 分片 + 总预算) P2-9 sys_log: 挂载改幂等 + 卷锁,defaultTask 2s 重试建 /log P2-10 sys_log: s_read_raw 纳入卷锁 try(读者/写者 unlink 互斥) P2-11 net_task: RECONFIG 改 osEventFlagsWait 原子取走 + 清除 P2-12 app_cfg: APP_*_ENABLE 如实标注为未接线占位,删“开关”说法 P2-13 freertos: 产品构建 vTaskDelete(testTask) 回收 8KB CCM 栈 P2-15 download.bat: 先删旧日志 + 增“未产出日志”判定 P2-13 用 USER CODE 区回收而非生成区 #if 门控:生成区改动会被 CubeMX 重生成覆盖,本方案重生成不丢。P2-12 因产品定义 B0 未定、APP_NET_ENABLE 无单一接线点,按修复建议备选(删“开关”说法)处理。 验证:@build 0 错 0 警;主机单测 watchdog 39/39 + sys_clock 100/100 + cfg_file 149/149(新增 TC-CFG-044);pre-commit --all-files 17/17。
This commit is contained in:
+16
-5
@@ -1,11 +1,18 @@
|
||||
/*
|
||||
* 模块名称:Application Configuration
|
||||
* 模块功能:应用层全局配置宏,控制功能模块的编译开关
|
||||
* 模块功能:应用层全局配置宏(**预留占位,当前均未接线**)
|
||||
* 适用平台:STM32F407ZGTx
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-19
|
||||
* 修改记录:
|
||||
* v1.0 2026-07-19 王建锋 创建初始版本
|
||||
* v1.1 2026-10-04 王建锋 P2-12:下列宏此前被表述为"功能模块使能开关",但全库
|
||||
* 零代码引用(grep 仅本文件有定义)——"开关名存实亡"会
|
||||
* 误导评审者以为模块可裁剪。现如实标注为**预留占位**:
|
||||
* 当前**不门控任何代码**,各模块一律编译进产品。是否/
|
||||
* 如何接线取决于产品定义(待处理内容.md §2 阶段 B0),
|
||||
* 确认后再在真实初始化点(如 app_main.c 的 rs485_init /
|
||||
* tpafe5160_init)加门控;在此之前**不要**把它们当开关用。
|
||||
*/
|
||||
|
||||
#ifndef APP_CFG_H
|
||||
@@ -16,14 +23,18 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 系统时钟源选择
|
||||
* 0 - DWT CYCCNT(默认,0 外设开销)
|
||||
* 1 - TIM2(32-bit 定时器,独立于 DWT)
|
||||
* 系统时钟源选择(**预留占位,未接线**)
|
||||
* 0 - DWT CYCCNT(当前实现:console_log.c 直接使用 DWT,未读本宏)
|
||||
* 1 - TIM2(未实现)
|
||||
* 说明:本宏当前无任何消费点;实际的"与时间源无关硬上界"由 console_log.c 内联
|
||||
* 使能 DWT 实现,不经过本宏。
|
||||
*/
|
||||
#define APP_SYS_TIME_SRC 0
|
||||
|
||||
/*
|
||||
* 功能模块使能开关
|
||||
* 功能模块"预留开关"(**当前均未接线,不门控任何代码**)
|
||||
* 三个宏一律保持定义(= 模块编译进产品);它们**不产生任何编译期效果**,
|
||||
* 仅作为产品定义(B0)落定后接入真实门控的占位锚点。
|
||||
*/
|
||||
#define APP_NET_ENABLE
|
||||
#define APP_ADC_ENABLE
|
||||
|
||||
+78
-3
@@ -18,6 +18,9 @@
|
||||
* 回工厂默认 → 基线粘住 → 用户配置销毁链);save 输出上限
|
||||
* 由 CFG_OUT_MAX 收紧至 CFG_FILE_MAX_LEN;s_write_doc 写盘
|
||||
* 唯一闸口拒写超长文档;load/poll 超长处置补自检留痕
|
||||
* v1.3 2026-10-04 王建锋 P2-1:受管键行超 CFG_LINE_TEST_MAX 时拒绝保存——
|
||||
* 原实现 Pass1 静默跳过超长行,Pass2 遂在段头补写新 KV 且
|
||||
* 原文超长行原样保留 → 同一键出现两行(旧值残留、静默截断)
|
||||
* 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save,见 P1-3;
|
||||
* 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证
|
||||
* (见《配置文件设计说明》D15 A+ 方案)
|
||||
@@ -822,13 +825,67 @@ static int s_surgical(char *p_in, size_t in_len, char *p_out, size_t out_cap, si
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:判断一行(非修改式)的键名是否为指定段的受管键
|
||||
* 入口参数:p_line - 行文本(只读) const char *
|
||||
* i_sec - 段索引 int
|
||||
* 返回值:1 - 是受管键;0 - 不是(无 '=' 或键名不匹配) int
|
||||
* 限定条件:仅比较 '=' 前的键名(大小写无关、剥尾部空白),不解析值
|
||||
* 函数说明:P2-1 用于"超长行是否落在受管键上"的判定(值可能很长,无法拷入定长缓冲);
|
||||
* 非修改式,故可安全用于仍供 Pass2 原样保留的行
|
||||
*/
|
||||
static int s_line_key_managed(const char *p_line, int i_sec) {
|
||||
const char *p_eq = strchr(p_line, '=');
|
||||
size_t kl;
|
||||
int k;
|
||||
|
||||
if (p_eq == NULL) {
|
||||
return 0;
|
||||
}
|
||||
kl = (size_t)(p_eq - p_line);
|
||||
while (kl > 0u && (p_line[kl - 1u] == ' ' || p_line[kl - 1u] == '\t')) {
|
||||
kl--;
|
||||
}
|
||||
for (k = 0; k < (int)s_sections[i_sec].key_count; k++) {
|
||||
const char *p_name = s_sections[i_sec].p_keys[k].name;
|
||||
size_t i = 0u;
|
||||
int eq = 1;
|
||||
|
||||
while (i < kl) {
|
||||
char ca = p_line[i];
|
||||
char cb = p_name[i];
|
||||
|
||||
if (ca >= 'A' && ca <= 'Z') {
|
||||
ca = (char)(ca + ('a' - 'A'));
|
||||
}
|
||||
if (cb >= 'A' && cb <= 'Z') {
|
||||
cb = (char)(cb + ('a' - 'A'));
|
||||
}
|
||||
if (cb == '\0' || ca != cb) {
|
||||
eq = 0;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (eq != 0 && p_name[i] == '\0') {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:保存 Pass 1——定位段头行号与各受管键行号
|
||||
* 入口参数:p_in - 原文本(可修改) char *
|
||||
* 返回值:无
|
||||
* 返回值:0 - 成功;-1 - 受管键行超 CFG_LINE_TEST_MAX(拒绝保存,见 P2-1) int
|
||||
* 限定条件:行末被 NUL 化且不复原——调用方须传一次性副本
|
||||
* 函数说明:P2-1(2026-10-04):受管键行超过 CFG_LINE_TEST_MAX 时**不能**静默跳过
|
||||
* ——Pass2 会因该行未登记而在段头补写新 KV,同时原文超长行原样保留,导致
|
||||
* 同一键出现两行(旧值残留 + 新值,解析取谁取决于行序)。故此处显式拒绝:
|
||||
* 返回 -1,调用方中止保存、原文件不动、自检留痕。非受管键的超长行照旧跳过
|
||||
* (属未知内容,Pass2 原样保留,无冲突)。
|
||||
*/
|
||||
static void s_surgical_pass1(char *p_in) {
|
||||
static int s_surgical_pass1(char *p_in) {
|
||||
char *p = p_in;
|
||||
int line_no = 0;
|
||||
int i;
|
||||
@@ -883,8 +940,16 @@ static void s_surgical_pass1(char *p_in) {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i_cur >= 0) {
|
||||
/* 超长行(> CFG_LINE_TEST_MAX):若其键名为本段受管键 → 拒绝保存(P2-1);
|
||||
* 否则按未知内容放过(Pass2 原样保留) */
|
||||
if (s_line_key_managed(p_line, i_cur) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
line_no++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1091,7 +1156,17 @@ int cfg_file_save(void) {
|
||||
}
|
||||
(void)memcpy(p_in1, p_in, in_len + 1u);
|
||||
|
||||
s_surgical_pass1((char *)p_in1);
|
||||
if (s_surgical_pass1((char *)p_in1) != 0) {
|
||||
/* P2-1:受管键行超 CFG_LINE_TEST_MAX → 中止保存(原文件不动)。此类超长行
|
||||
* 只可能来自手工编辑(代码生成的受管 KV 行恒 <127B);若强行外科保存会
|
||||
* 产生重复键行,故宁可拒存并留痕,也不静默写坏。 */
|
||||
cfg_store_free(p_in1);
|
||||
cfg_store_free(p_in);
|
||||
DBG_ERROR("save: managed key line > %uB, aborted (file untouched)",
|
||||
(unsigned)CFG_LINE_TEST_MAX);
|
||||
syslog_post(SYSLOG_SELFTEST, "cfg save line too long");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cfg_store_free(p_in1);
|
||||
|
||||
|
||||
+45
-4
@@ -16,6 +16,12 @@
|
||||
* v1.3 2026-10-02 王建锋 新增 sys_log_evidence():把看门狗停喂现场
|
||||
* 证据落 /log/wdev.log(掉电不丢)+ flush 诱导
|
||||
* (round 7 纯取证,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)
|
||||
* v1.4 2026-10-04 王建锋 P2-9:syslog_mcu_mounted() 改为幂等 + 卷锁保护;
|
||||
* defaultTask 每 2s 复查,建 /log 曾失败时自动重试
|
||||
* (原"仅调用一次"→ 失败后 drain 永久 no-op)。
|
||||
* P2-10:s_read_raw() 纳入卷锁 try 语义,与写者
|
||||
* s_append 的"容量满 unlink 重建"互斥,消除
|
||||
* "UI 读记录(FA_READ) 时文件被 unlink"竞态窗口。
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -49,6 +55,7 @@ static volatile uint8_t s_preboot_cnt; /* 数组条目数 */
|
||||
static osMessageQueueId_t s_queue; /* 运行期消息队列(syslog_drain 首次调用时惰性创建) */
|
||||
static const osMessageQueueAttr_t s_queue_attr = { .name = "syslog" };
|
||||
static volatile uint8_t s_fs_ready; /* 卷挂载 + /log 目录就绪后置位 */
|
||||
static volatile uint8_t s_mount_fail_reported; /* P2-9:建目录失败日志去重(成功复位) */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
@@ -153,18 +160,28 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
* 入口参数:id - 日志 ID syslog_id_t
|
||||
* idx - 记录序号(0 起) uint32_t
|
||||
* p_rec- 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t *
|
||||
* 返回值:0 - 成功;-1 - 越界/读失败 int
|
||||
* 返回值:0 - 成功;-1 - 越界/读失败/卷忙 int
|
||||
* 函数说明:P2-10(2026-10-04):读者纳入卷锁(try),与写者 s_append 的
|
||||
* "容量满 unlink 重建"互斥——原实现读时无锁,写者 unlink 与读者
|
||||
* open/read 可交错,读到的可能是半删除态文件。try 语义(timeout=0):
|
||||
* 本路径可由 uiTask 调用,uiTask 在 required_mask 内、须每 100ms 喂
|
||||
* 心跳,绝不能阻塞等锁;busy = 本次读失败(UI 下轮刷新重读,非故障)。
|
||||
*/
|
||||
static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
|
||||
FIL f;
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
int rc;
|
||||
|
||||
if (p_rec == NULL || idx >= syslog_count(id)) {
|
||||
return -1;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -1; /* 卷被占用:本次读失败(非故障,下轮重读) */
|
||||
}
|
||||
fr = f_open(&f, s_paths[id], FA_READ);
|
||||
if (fr != FR_OK) {
|
||||
vol_lock_release();
|
||||
return -1;
|
||||
}
|
||||
fr = f_lseek(&f, (FSIZE_t)idx * SYSLOG_REC_SIZE);
|
||||
@@ -172,7 +189,9 @@ static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
|
||||
fr = f_read(&f, p_rec, SYSLOG_REC_SIZE, &br);
|
||||
}
|
||||
(void)f_close(&f);
|
||||
return (fr == FR_OK && br == SYSLOG_REC_SIZE) ? 0 : -1;
|
||||
rc = (fr == FR_OK && br == SYSLOG_REC_SIZE) ? 0 : -1;
|
||||
vol_lock_release();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -194,20 +213,42 @@ int syslog_mcu_init(void) {
|
||||
* 函数功能:卷挂载成功后的一次性处理(建 /log 目录 + 写"上电/重启"分类记录)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:defaultTask f_mount/f_mkfs 成功且置位 SYS_EVT_STORAGE_READY 前调用一次
|
||||
* 限定条件:defaultTask f_mount/f_mkfs 成功后调用;**可重复调用(幂等)**——
|
||||
* 产品运行期 defaultTask 每 2s 复查一次(见下 P2-9 说明)
|
||||
* 函数说明:P2-9(2026-10-04):原实现"仅调用一次",若此处建 /log 失败则
|
||||
* s_fs_ready 恒 0 → syslog_drain 永久 no-op(运行期日志与自检全部
|
||||
* 滞留/丢弃),且无任何重试。现改为幂等:s_fs_ready 已置位则立即
|
||||
* 返回;否则重试建目录。defaultTask 在 STORAGE_READY 置位后每 2s
|
||||
* 调用本函数——卷已就绪但日志后端未就绪时自动补建目录。
|
||||
* FS 操作纳入卷锁 try:运行期重试时 ftpTask 可能正操作卷,try 失败
|
||||
* = 本轮顺延(下轮再试),绝不阻塞(本任务 = 看门狗喂狗点)。
|
||||
*/
|
||||
void syslog_mcu_mounted(void) {
|
||||
FILINFO finfo;
|
||||
uint32_t last = 0u;
|
||||
const char *desc = "power on";
|
||||
|
||||
if (s_fs_ready != 0u) {
|
||||
return; /* 幂等:已就绪 */
|
||||
}
|
||||
/* 卷锁 try:运行期重试时与 ftpTask 卷访问串行;busy = 本轮顺延 */
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return;
|
||||
}
|
||||
if (f_stat("/log", &finfo) != FR_OK) {
|
||||
FRESULT fr = f_mkdir("/log");
|
||||
if (fr != FR_OK && fr != FR_EXIST) {
|
||||
DBG_ERROR("mkdir /log fail (%d)", (int)fr);
|
||||
/* 失败日志去重:持续失败只报第一次(防 2s 周期刷屏),成功后复位 */
|
||||
if (s_mount_fail_reported == 0u) {
|
||||
s_mount_fail_reported = 1u;
|
||||
DBG_ERROR("mkdir /log fail (%d), will retry", (int)fr);
|
||||
}
|
||||
vol_lock_release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
vol_lock_release();
|
||||
s_mount_fail_reported = 0u;
|
||||
s_fs_ready = 1u;
|
||||
|
||||
/* 上电/重启分类:末条 epoch 距 now < SYSLOG_REBOOT_WINDOW_S → 重启,否则上电 */
|
||||
|
||||
@@ -217,6 +217,13 @@ void default_task_func(void *argument) {
|
||||
syslog_post(SYSLOG_SELFTEST, "filesystem fault");
|
||||
}
|
||||
}
|
||||
/* P2-9:卷已就绪(STORAGE_READY)但日志后端未就绪(建 /log 曾失败,
|
||||
* s_fs_ready 恒 0 → syslog_drain 永久 no-op)→ 2s 周期重试(幂等;
|
||||
* 就绪后 syslog_mcu_mounted 立即返回,仅一次 volatile 读)。 */
|
||||
if (sys_evtHandle != NULL
|
||||
&& (osEventFlagsGet(sys_evtHandle) & SYS_EVT_STORAGE_READY) != 0u) {
|
||||
syslog_mcu_mounted();
|
||||
}
|
||||
cfg_file_poll();
|
||||
#endif
|
||||
}
|
||||
|
||||
+23
-18
@@ -240,26 +240,31 @@ void net_task_func(void const *arg) {
|
||||
task_wd_beat(TASK_WD_NET);
|
||||
|
||||
/* UI 保存网口配置后请求立即应用:在此重建网络(新 IP/掩码/网关/MAC)。
|
||||
* 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。 */
|
||||
if (sys_evtHandle != NULL
|
||||
&& (osEventFlagsGet(sys_evtHandle) & SYS_EVT_NET_RECONFIG) != 0u) {
|
||||
(void)osEventFlagsClear(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
DBG_INFO("netTask: apply new net config (re-init)");
|
||||
if (net_stack_bring_up() == 0) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
|
||||
* 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。
|
||||
* P2-11(2026-10-04):用 osEventFlagsWait(timeout=0) 原子"取走并清除"RECONFIG
|
||||
* 位,替换原 Get+Clear 两步;原写法在 Get 与 Clear 之间若有生产者在同一位上
|
||||
* Set,该次请求会被 Clear 一并吞掉(丢事件窗口)。 */
|
||||
if (sys_evtHandle != NULL) {
|
||||
uint32_t evt = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_RECONFIG,
|
||||
osFlagsWaitAny, 0U);
|
||||
if ((evt & osFlagsError) == 0u) {
|
||||
DBG_INFO("netTask: apply new net config (re-init)");
|
||||
if (net_stack_bring_up() == 0) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
|
||||
s_reinit_retry_at = 0U;
|
||||
} else {
|
||||
/* P1-8(2026-10-02):重建失败 → 自检留痕 + 30s 延迟重试(重事件位),
|
||||
* 新配置不再静默丢失(原实现仅一条 DBG_ERROR 后丢弃) */
|
||||
DBG_ERROR("netTask: re-init FAIL, retry in 30s");
|
||||
(void)syslog_post(SYSLOG_SELFTEST, "net reinit fail");
|
||||
s_reinit_retry_at = (uint32_t)HAL_GetTick() + 30000U;
|
||||
}
|
||||
} else if ((s_reinit_retry_at != 0U)
|
||||
&& ((int32_t)((uint32_t)HAL_GetTick() - s_reinit_retry_at) >= 0)) {
|
||||
/* P1-8:30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */
|
||||
s_reinit_retry_at = 0U;
|
||||
} else {
|
||||
/* P1-8(2026-10-02):重建失败 → 自检留痕 + 30s 延迟重试(重事件位),
|
||||
* 新配置不再静默丢失(原实现仅一条 DBG_ERROR 后丢弃) */
|
||||
DBG_ERROR("netTask: re-init FAIL, retry in 30s");
|
||||
(void)syslog_post(SYSLOG_SELFTEST, "net reinit fail");
|
||||
s_reinit_retry_at = (uint32_t)HAL_GetTick() + 30000U;
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
}
|
||||
} else if ((sys_evtHandle != NULL) && (s_reinit_retry_at != 0U)
|
||||
&& ((int32_t)((uint32_t)HAL_GetTick() - s_reinit_retry_at) >= 0)) {
|
||||
/* P1-8:30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */
|
||||
s_reinit_retry_at = 0U;
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
}
|
||||
|
||||
net_poll();
|
||||
|
||||
Reference in New Issue
Block a user