Files
STM32F4-Base/App/task/net_task.c
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00

273 lines
13 KiB
C
Raw 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.
/*
* 模块名称:Network Application Task
* 模块功能:CH395F 协议栈初始化 + 轮询 + TCP/UDP 业务处理
* 与 net_socket.h 配合,在 FreeRTOS 独立任务中运行
* 适用平台:STM32F407ZGTx + CH395F
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-07-19 王建锋 migrate from freertos.c StartNetTask
* v1.2 2026-07-21 王建锋 网络初始化移入本任务(net_init + TCP 监听)
* v1.3 2026-07-22 王建锋 单连接模式重构,增加大文件/边界/KeepAlive 测试
* v1.4 2026-09-14 王建锋 新增自检埋点:PHY 链路 3s 未建立 → sys_log "comms fault"
* v1.5 2026-09-15 王建锋 配置加载移交 defaultTask(配置引擎属主);
* 本任务只读 RAM 副本
* v1.6 2026-09-15 王建锋 入口加 CH395F 硬件层测试模式自挂起守卫
* (基础线程无条件创建,该模式下不运行);
* freertos.c USER CODE 区重复创建已删除
* v1.7 2026-10-02 王建锋 初始组网失败/重建失败 → 自检记录 + 30s 周期重试
* (原实现:初始失败 = netTask 无限驻留;重建失败 = 静默丢配置)
* v1.8 2026-10-10 王建锋 任务耗时测量埋点:net_poll() 单独计时——
* 本任务每 ~1ms 分派一次消息队列,若驱动轮询被拉长,
* 其它任务(尤其 UI 的 net_accept 往返)的等待随之拉长
* v1.9 2026-10-21 王建锋 删除 net_task_init():空壳入口(体内只有一条
* DBG_INFO),全库无调用点,而函数头却写"创建 TCP
* Server 监听 Socket,注册回调等"——描述与实际不符,
* 监听实际在 net_stack_bring_up() 与各服务里做
*/
#include <string.h>
#include "net_task.h"
#include "cmsis_os.h"
#include "net_socket.h"
#include "net_select.h"
#include "net_cfg.h"
#include "ch395f.h"
#include "sys_events.h"
#include "sys_log.h"
#include "test_config.h" /* 测试模式宏(ENABLE_HW_LAYER_TESTS);产品构建全未定义,无作用 */
#include "task_watchdog.h"
#include "task_prof.h" /* 探针:net_poll 耗时 */
#define DBG_TAG "[NET_TASK]"
#include "dbg_log.h"
/*
* 函数功能:"立即应用网口配置"平台回调(注册给 net_cfg)
* 入口参数:无
* 返回值:无
* 限定条件:保存写盘成功后经 net_cfg_request_apply() 调用(MCU 上由
* defaultTask 上下文调用;任意任务上下文均安全)
* 函数说明:只置事件位;真正的重建在主循环内完成(CH395F SPI 归 netTask 独占,
* UI 不得直接操作)
*/
/* 重建失败延迟重试的截止 tick;0 = 无待重试 */
static uint32_t s_reinit_retry_at;
static void net_reconfig_request_cb(void) {
if (sys_evtHandle != NULL) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
}
}
/*
* 函数功能:CH395F 协议栈一次性初始化(两种构建模式共用)
* 入口参数:无
* 返 回 值:0 - 成功,-1 - 失败
* 函数说明:net_cfg 已由 defaultTask 在就绪屏障前加载进 RAM 副本(配置引擎
* 属主 = defaultTask,本任务不触碰配置文件引擎)→ 只读副本组网 →
* net_init → MAC 回写
* 配置模块 → MAC 打印 → PHY 强制 100M 全双工 → 广播网络就绪事件。
* NET 层模式由 netTask 调用;硬件层模式(阶段 1~5)netTask 不创建,
* 由 testTask 调用后独占 CH395F。
*/
int net_stack_bring_up(void) {
const net_cfg_t *cfg;
char ip[16];
char mask[16];
char gw[16];
DBG_INFO("=== Network Init ===");
/* 网络配置:defaultTask 已在 SYS_EVT_STORAGE_READY 屏障前置入 RAM 副本
* (首启/文件缺失/损坏 = 工厂默认,与既有硬编码组网值一致);
* 此处只读副本,不触碰配置文件引擎 */
cfg = net_cfg_get();
net_cfg_fmt_ipv4(cfg->ip, ip, sizeof(ip));
net_cfg_fmt_ipv4(cfg->mask, mask, sizeof(mask));
net_cfg_fmt_ipv4(cfg->gw, gw, sizeof(gw));
if (net_init(ip, mask, gw, cfg->mac) != 0) {
DBG_ERROR("net init: FAIL");
return -1;
}
DBG_INFO("net init: OK");
uint8_t mac[6];
ch395f_get_mac_addr(mac);
DBG_INFO("MAC: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
/* 回写芯片实际 MAC 到配置模块(UI "网口设置"页只读影子,
* 不直接触 CH395F SPI——SPI 归 netTask 集中处理) */
net_cfg_set_active_mac(mac);
uint8_t ipinf[20];
ch395f_get_ip_inf(ipinf);
DBG_INFO("IP_INF: IP=%d.%d.%d.%d GW=%d.%d.%d.%d MASK=%d.%d.%d.%d",
ipinf[0], ipinf[1], ipinf[2], ipinf[3],
ipinf[4], ipinf[5], ipinf[6], ipinf[7],
ipinf[8], ipinf[9], ipinf[10], ipinf[11]);
ch395f_set_phy(CH395F_PHY_100M_FULL);
/* 强制 PHY 模式后链路会短暂掉线再重建,CH395F 会锁存 GINT_STAT_PHY_CHANGE(0x04) 全局中断标志。
* 这里只轮询 ch395f_get_phy_status() 确认链路是否已真正建立——**不读全局中断寄存器**:
* 它读即清,唯一读者 = net_poll(F-NET-7,用户裁定 2026-10-13 测试代码同样禁自行读取)。
* 锁存的 PHY_CHANGE 会在 net_poll 首轮被正常消费(喂驱动链路态缓存,无害)。
* 链路一就绪(phy != DISCONN)即跳出;最多等约 3s(60 × 50ms),超时仍按实际状态继续(由下方日志暴露问题)。 */
uint8_t phy = CH395F_PHY_DISCONN;
for (unsigned int i = 0U; i < 60U; i++) {
phy = ch395f_get_phy_status();
if (phy != CH395F_PHY_DISCONN) {
break;
}
/* 心跳埋点:本循环单次最长约 1.1s(含 PHY 仍未就绪的 60 次迭代里
* 每次 50ms 延时叠加),远超看门狗 100ms 抽样窗——必须在延时段补心跳,
* 否则 PHY 未插网线时本任务会被误判为卡死(见设计说明 §4.4 长耗时白名单) */
task_wd_beat(TASK_WD_NET);
osDelay(50);
}
DBG_INFO("PHY: %s",
(phy == CH395F_PHY_100M_FULL) ? "100M FULL" :
(phy == CH395F_PHY_100M_HALF) ? "100M HALF" :
(phy == CH395F_PHY_10M_FULL) ? "10M FULL" :
(phy == CH395F_PHY_10M_HALF) ? "10M HALF" :
(phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN");
/* 自检埋点:链路超时未建立 → 记入自检记录(仅故障才写) */
if (phy == CH395F_PHY_DISCONN) {
syslog_post(SYSLOG_SELFTEST, "comms fault");
}
/* 网络已重建完成、对外可用:递增代次号,通知各 TCP 服务(FTP/对时/远程显示)
* 旧 Socket 已随芯片复位失效、应重建监听 */
net_generation_inc();
return 0;
}
/*
* 函数功能:网络任务主函数
* 入口参数:arg - FreeRTOS 传入参数(未使用)
* 返 回 值:无
* 函数说明:等待存储就绪(就绪屏障 = 卷挂载 + 网络配置加载完成,10s 超时
* 回退工厂默认,存储故障不阻塞组网)→ net_stack_bring_up() 初始化
* CH395F 协议栈,
* 再进入轮询循环处理网络事件。
* 本任务 = 基础线程:CubeMX 生成区无条件创建(全构建唯一实例);
* CH395F 硬件层测试模式(阶段 1~5)下入口自挂起(不 bring_up、
* 不轮询、不广播 NET_READY),由 testTask 独占 CH395F。
*/
void net_task_func(void const *arg) {
(void)arg;
task_wd_bind(TASK_WD_NET); /* FTL 心跳路由:登记本任务句柄(入口一次) */
/* 硬件层测试模式:testTask 独占 CH395F,本任务自挂起(基础线程恒创建,
* 该模式下"不运行"):不 bring_up、不 net_poll、不广播 NET_READY;
* ftpTask/timeSyncTask 将永久阻塞于屏障 = 预期行为(见《项目启动顺序管理》) */
#if defined(ENABLE_HW_LAYER_TESTS)
DBG_INFO("netTask parked: HW-layer test mode, CH395F owned by testTask");
for (;;) {
/* 该模式下本任务已被 task_wd_required_mask() 剔除,喂心跳无副作用;
* 保留埋点以免掩码配置变更时意外误杀 */
task_wd_beat(TASK_WD_NET);
osDelay(1000);
}
#endif
/* 配置在 NAND 的 FatFS 卷上:等存储就绪再读(取代原 2s 魔法延时);
* 超时(存储异常)继续以工厂默认组网,不卡网络启动 */
if (sys_evtHandle != NULL) {
/* osFlagsNoClear:就绪位是状态,不能被本任务消费(ftpTask 等仍要 WaitAll 两位)。
* 2026-10-02 台架修复:10s 超时等待改 100ms 分片 + 每片喂心跳——原实现
* 在等待期无心跳:若存储 bring-up 失败,本等待跑满 10s 超时,而 IWDG
* 此时已启动(defaultTask 在 bring-up 后即启动),NET 位长期缺失 →
* 必需掩码不可达 → 复位循环,存储自愈的 2s 重试根本没有机会。
* 分片喂位保证"存储未就绪"降级态下 NET 位持续新鲜。 */
uint32_t rounds_left = 100u; /* 100 × 100ms = 原 10s 超时语义 */
for (;;) {
uint32_t r = osEventFlagsWait(sys_evtHandle, SYS_EVT_STORAGE_READY,
osFlagsWaitAny | osFlagsNoClear, 100u);
if ((r & osFlagsError) == 0u) {
break; /* 存储就绪 */
}
task_wd_beat(TASK_WD_NET); /* 看门狗:分片喂心跳(屏障等待态) */
if ((--rounds_left) == 0u) {
break; /* 10s 超时:工厂默认组网,不阻塞网络启动 */
}
}
}
/* 初始组网失败不自驻留——自检台账留痕 + 30s 周期重试
* (1s 分片、每片喂心跳):线缆/芯片恢复即可当场完成组网;原 for(;;)osDelay
* 会令看门狗 NET 位持续陈旧,并阻塞全部下游就绪屏障 */
for (;;) {
if (net_stack_bring_up() == 0) {
break;
}
DBG_ERROR("netTask: init FAIL, retry in 30s");
(void)syslog_post(SYSLOG_SELFTEST, "net init fail");
for (uint32_t i = 0U; i < 30U; i++) {
osDelay(1000U);
task_wd_beat(TASK_WD_NET);
}
}
/* 广播"网络就绪",唤醒等待它的 ftpTask / start_test_task / timeSyncTask 等 */
if (sys_evtHandle != NULL) {
osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
}
/* 注册"立即应用网口配置"回调:UI 保存成功后经 net_cfg_request_apply() 触发 */
net_cfg_set_apply_cb(net_reconfig_request_cb);
DBG_INFO("netTask: started (single-socket mode)");
/* ==================== 主轮询循环 ==================== */
for (;;) {
/* 心跳:本任务在监视掩码内(ENABLE_HW_LAYER_TESTS 下由 task_wd_required_mask
* 剔除,本埋点无副作用) */
task_wd_beat(TASK_WD_NET);
/* UI 保存网口配置后请求立即应用:在此重建网络(新 IP/掩码/网关/MAC)。
* 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。
* 用 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 {
/* 重建失败 → 自检留痕 + 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)) {
/* 30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */
s_reinit_retry_at = 0U;
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
}
}
/* P2 探针:驱动轮询单独计时。本循环每 ~1ms 一次,是 netTask 回执
* (UI 的 net_accept 在等它)的节奏来源;此值变大 = UI 心跳被拖长 */
task_prof_enter(TASK_PROF_NET_POLL);
net_poll();
task_prof_exit(TASK_PROF_NET_POLL);
net_process_messages();
osDelay(1);
}
}