diff --git a/AGENTS.md b/AGENTS.md
index e41b18f7..9269c94b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -76,6 +76,7 @@
## 构建与验证
+- **门禁时机(用户裁定 2026-10-13)**:`@build` / `scripts/ci_local.py` / `check_doc_links.py` **只在准备提交时跑**(用户让 commit 后统一跑一遍),**不是每次中间改动后都跑**——多步改动期间直接改即可,验证集中在提交门禁;`@test` 硬件闭环仍按任务需要跑
- `@build` = `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(仓库根执行)。退出码 **0=PASS / 1=构建失败 / 2=环境错误(UV4/工程/档案)/ 3=用法 / 4=build 锁被占用**;日志 `MDK-ARM/build_log.txt`;换机必设 `DTU_KEIL_UV4` → 细节 **`keil-build` skill** + `docs/环境与部署/本地工具路径环境变量方案.md`
- `@test` = `& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\run_boot_test.py`:开串口+清日志 → 烧录(**固件身份戳**保证烧进去的就是这份构建、**回读校验**)→ wait-idle → 采日志 → 标记校验(含启动锚点计数)。退出码 **0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法**;戳提取规则、锚点计数判据与按模块定制标记见 `docs/测试说明/自动化测试流程.md`
- Forbidden 标记含 `[ERR]`:**四个 Fault 的崩溃日志都以 `[ERR]` 开头 → 出现 Fault 即本次不通过**(刻意)
diff --git a/App/sync/time_sync.c b/App/sync/time_sync.c
index 00351afd..0939f36b 100644
--- a/App/sync/time_sync.c
+++ b/App/sync/time_sync.c
@@ -124,7 +124,7 @@ static int time_sync_open_listener(void) {
* 入口参数:conn_sock - 已建立的连接 Socket 描述符(单连接模式下即监听 Socket 自身)
* 返回值:无
* 函数说明:pending 读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后
- * 写回 RTC。**不在此 close**(N34 阶段1-5:单连接模式下 conn_sock 即
+ * 写回 RTC。**不在此 close**(自愈收敛阶段1-5:单连接模式下 conn_sock 即
* 监听 fd,close 连监听一起毁、重建窗 ~0.3-1s;net 层 auto_relisten
* 已把同 fd 收回监听,外层直接续 accept——lftpd 样板)。每次等待 =
* 一次挂起请求(netTask 零占用,L3 pending 化),单次超时 = 剩余总预算
@@ -180,14 +180,14 @@ static void time_sync_handle_conn(int conn_sock) {
DBG_INFO("time_sync: wall clock updated (epoch=%lu)", (unsigned long)epoch);
}
} else {
- /* N34 阶段 3(E-02 收口,实验方案 E13 §5):本层放弃会话 → 主动拆除。
+ /* 自愈收敛阶段 3(E-02 收口,《网络自愈架构设计》§5.2 E13):本层放弃会话 → 主动拆除。
* 挂起但 TCP 仍活的客户端(PC 内核自动 ACK keepalive,芯片僵尸判不死)
* 会永久占住单连接监听;EOF 路径 drop 幂等返 0(net 层已 relisten)。
* 模拟器多连接后端 conn≠ls,由 sim 实现按数据 fd 关闭。 */
(void)net_drop_conn(conn_sock);
}
- /* N34 阶段1-5:原 net_close(conn_sock) 已删——单连接模式 conn==监听 fd,
+ /* 自愈收敛阶段1-5:原 net_close(conn_sock) 已删——单连接模式 conn==监听 fd,
* close 毁监听(AGENTS 模块约定:EOF 时应用层不得 close);net 层
* auto_relisten 已把同 fd 收回 LISTENING,外层循环直接续 accept。
* 模拟器多连接后端 conn≠ls,由调用方(外层)负责关数据 fd。 */
@@ -199,7 +199,7 @@ static void time_sync_handle_conn(int conn_sock) {
* 返回值:无
* 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列
* 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> **同 fd 续
- * accept**(N34 阶段1-5:会话收尾不 close,监听跨会话常驻,仅网络
+ * accept**(自愈收敛阶段1-5:会话收尾不 close,监听跨会话常驻,仅网络
* 代次变化时重建)。全部 socket 操作经消息队列由 netTask 串行执行,
* 与本任务/其它网络任务并发安全。
*/
@@ -226,7 +226,7 @@ void time_sync_task(void *argument) {
}
}
- /* 监听 fd 跨会话常驻(N34 阶段1-5):仅首次/网络代次变化/被毁时重建 */
+ /* 监听 fd 跨会话常驻(自愈收敛阶段1-5):仅首次/网络代次变化/被毁时重建 */
int ls = -1;
for (;;) {
@@ -266,7 +266,7 @@ void time_sync_task(void *argument) {
}
time_sync_handle_conn(conn);
- /* N34 阶段1-5:会话收尾不 close(单连接模式 conn==ls,close 连监听
+ /* 自愈收敛阶段1-5:会话收尾不 close(单连接模式 conn==ls,close 连监听
* 一起毁);net 层 auto_relisten 已把同 fd 收回 LISTENING,下轮循环
* 直接续 accept。模拟器多连接后端(conn≠ls)才关数据 fd。 */
if (conn != ls) {
diff --git a/App/task/iec104_serv.c b/App/task/iec104_serv.c
index 0d094598..0b9fc928 100644
--- a/App/task/iec104_serv.c
+++ b/App/task/iec104_serv.c
@@ -30,7 +30,7 @@
/* 接收搬运缓冲(静态,不入栈;link 层内部另有 512B 重组缓冲) */
#define IEC104_RX_SZ 128u
/* 兜底节拍:门控判"无数据"时每 32 轮无条件收一次(ui_net_sink RM 先例,
- * 防 RECV_OK 中断丢失把数据永久藏住);须为 2 的幂 */
+ * 防 RECV_OK 事件未收到把数据永久藏住);须为 2 的幂 */
#define IEC104_LIVENESS_DIV 32u
/* 连续发送失败阈值:同 ui_net_sink E-04(短写=对端收坏帧) */
#define IEC104_SEND_FAIL_MAX 3u
@@ -224,7 +224,7 @@ static void s_serv_poll(void) {
if (s_client < 0) {
int c;
- /* N34 阶段1(F-NET-4):门控从 net_can_accept(只认 ESTABLISHED,已退役)
+ /* 自愈收敛阶段1(F-NET-4):门控从 net_can_accept(只认 ESTABLISHED,已退役)
* 迁 net_listener_armed——旧门控把 LISTENING 窗口全挡掉,:2404 的丢
* CONNECT 探针(net_accept_locked 内)因此从不运行(与已修的 :7003
* 同型);armed=ESTABLISHED 待 accept / LISTENING /
@@ -258,7 +258,7 @@ static void s_serv_poll(void) {
s_liveness_ctr = 0u;
}
- /* N34 阶段 1.5:DONTWAIT 转正为具名 API net_recv_nb——本任务在 IWDG
+ /* 自愈收敛阶段 1.5:DONTWAIT 转正为具名 API net_recv_nb——本任务在 IWDG
* required_mask 内且有 100ms 周期职责(喂狗/链路节拍/上送),禁止无限
* 阻塞 recv(文档契约见 net_socket.h net_recv 注释),非阻塞轮询是义务 */
n = net_recv_nb(s_client, s_rx, (int)sizeof(s_rx));
diff --git a/App/task/net_task.c b/App/task/net_task.c
index e4f8d19e..8dcfb85b 100644
--- a/App/task/net_task.c
+++ b/App/task/net_task.c
@@ -110,14 +110,12 @@ int net_stack_bring_up(void) {
ch395f_set_phy(CH395F_PHY_100M_FULL);
/* 强制 PHY 模式后链路会短暂掉线再重建,CH395F 会锁存 GINT_STAT_PHY_CHANGE(0x04) 全局中断标志。
- * 这里轮询该标志等待"链路状态变化"(每次读取即清除),同时用 ch395f_get_phy_status()
- * 确认链路是否已真正建立——避免因 PHY_CHANGE 只反映"掉线"边沿而误判就绪。
- * 链路一就绪(phy != DISCONN)即跳出;最多等约 3s(60 × 50ms),超时仍按实际状态继续(由下方日志暴露问题)。
- * 注:此处读取全局中断状态属"启动期一次性链路等待",net_poll 尚未启动,不与其中断集中处理冲突;
- * NET 模式下该标志后续仍会被 net_poll 正常消费/清除(见 net_socket.c)。 */
+ * 这里只轮询 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++) {
- (void)ch395f_get_glob_int_status_all(); /* 读取并清除 PHY_CHANGE 等全局中断标志 */
phy = ch395f_get_phy_status();
if (phy != CH395F_PHY_DISCONN) {
break;
diff --git a/Drivers/BSP/CH395F/README.md b/Drivers/BSP/CH395F/README.md
index 49c78225..667a7835 100644
--- a/Drivers/BSP/CH395F/README.md
+++ b/Drivers/BSP/CH395F/README.md
@@ -22,7 +22,7 @@
| Trap | 问题 | 状态 |
|---|---|---|
| TC-NET-604 | "RX 窗口冻结" 判定为**非缺陷**(原报告系测试程序自吞数据 + 判读工具误判) | ✅ 结案 |
-| TRAP-NET-17 | **快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡**;修复 = `net_reconcile_tick()` 周期对账自愈 | ✅ 已修 |
+| TRAP-NET-17 | **快速复连事件面未收到 → 监听静默死亡**(E14 复核:非芯片丢事件,是读清消费竞态);修复 = `net_reconcile_tick()` 周期对账自愈 | ✅ 已修 |
| TRAP-NET-18 | **上传中止后首笔复连被静默截断(≤2048B)并误报成功**;修复 = 异常释放后复监听隔离排空 | ✅ 已修 |
> 完整台账与复现配方见 `docs/问题记录/CH395F_Trap_Records.md`(**改驱动前必读**)。
diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c
index a04d0212..38f2832a 100644
--- a/Drivers/BSP/CH395F/ch395f.c
+++ b/Drivers/BSP/CH395F/ch395f.c
@@ -1410,8 +1410,8 @@ void ch395f_get_unreach_info(uint8_t *p_buf) {
}
/* ============================================================================
- * N34 阶段 2 驱动原语(实施方案 docs/网络/N34-自愈审计.md §5.4;
- * 形态定案 docs/网络/N34-自愈审计.md §6 E2/E3/E4,2026-10-13 台架实证)
+ * 自愈收敛阶段 2 驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4;
+ * 形态定案锚点 同文档 §5.2 E2/E3/E4,2026-10-13 台架实证)
* 只加不接线:net_poll 全量刷新 / 两级预清改调用 / 链路 feed 接线 = 阶段 3。
* ========================================================================== */
diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h
index ba9da2d1..c4e4732e 100644
--- a/Drivers/BSP/CH395F/ch395f.h
+++ b/Drivers/BSP/CH395F/ch395f.h
@@ -160,7 +160,7 @@ extern "C" {
#define CH395F_CMD_SET_KEEPALIVE_INTVL 0x57U
#define CH395F_CMD_SET_KEEPALIVE_CNT 0x58U
#define CH395F_CMD_SET_KEEPALIVE_SN 0x59U
-/* N34 阶段0-9:EEPROM(0xE9..0xEB)/GPIO(0xEC/0xED)/SLEEP(0x03) 命令宏与
+/* 自愈收敛阶段0-9:EEPROM(0xE9..0xEB)/GPIO(0xEC/0xED)/SLEEP(0x03) 命令宏与
* GET_GLOB_INT_STATUS(0x29,只有 2 字节版 0x19 有实现) 死声明已删除——
* 全仓零引用核实于 2026-10-13;需要时按手册 §5.x 重新登记 */
@@ -198,7 +198,7 @@ extern "C" {
/*
* 功能参数标志位(CMD_SET_FUN_PARA,§5.52 表)
- * bit3 SOCKET_CLOSE 需芯片版本 ≥0x46(版本门控见 net_init,N34 阶段0-6);
+ * bit3 SOCKET_CLOSE 需芯片版本 ≥0x46(版本门控见 net_init,自愈收敛阶段0-6);
* bit1 TCP_SERVER 多连接模式——本项目单连接模型不用(红线,勿置 1);
* bit0 保留必须为 0(TRAP-NET-15 定案更正:0x09→0x08)
*/
@@ -238,7 +238,7 @@ extern "C" {
/*
* 全局中断状态位低字节(bit0~3 全局事件;§5.16——2 字节版
* GET_GLOB_INT_STATUS_ALL 低字节同语义。Socket 事件位(bit4 起)由
- * net_poll 按 1U<<(i+4) 计算,旧 SOCK0..3 单列宏已删(N34 阶段0-5,
+ * net_poll 按 1U<<(i+4) 计算,旧 SOCK0..3 单列宏已删(自愈收敛阶段0-5,
* 全仓零引用;1 字节版命令 0x29 亦已随死声明清理)
*/
#define CH395F_GINT_STAT_UNREACH 0x01U
@@ -378,7 +378,7 @@ ch395f_status_t ch395f_init(void);
/*
* 函数功能:设置 CH395 IP 地址(点分十进制序:p_ip[0]=第一段,如 192;
- * N34 阶段0-7 更正旧注"低字节在前"——调用方 parse_ip_string 产出
+ * 自愈收敛阶段0-7 更正旧注"低字节在前"——调用方 parse_ip_string 产出
* 点分数组直传、芯片按首段在前接受,台架多年 IP 生效实证)
* 入口参数:p_ip - 4 字节 IP 地址指针 uint8_t*
* 限定条件:指针非空,芯片已初始化
@@ -483,7 +483,7 @@ void ch395f_set_proto_type(uint8_t sock, uint8_t proto);
* p_ip - 4 字节目标 IP 指针 uint8_t*
* 限定条件:指针非空
* 函数说明:字节序与 ch395f_get_remot_ipp 输出一致(net_connect 直传其输出,
- * 台架出站连接往返实证;N34 阶段0-7 补注)
+ * 台架出站连接往返实证;自愈收敛阶段0-7 补注)
*/
void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip);
@@ -581,6 +581,12 @@ uint16_t ch395f_get_recv_len(uint8_t sock);
* 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7)
* 返回值:中断状态 uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6)
* 限定条件:芯片已初始化
+ * ⚠ 护栏(F-NET-7,I7 不变量):本寄存器**读即清**且含全部 Socket 事件位——
+ * 任何未配对 SINT 消费的读取都会吞掉其它 Socket 的事件通知。
+ * **唯一读者 = net_poll(net_socket.c);测试/台架代码同样禁自行读取**
+ * (用户裁定 2026-10-13)——等事件一律直读本 Socket ch395f_get_sock_int_status()。
+ * 唯一例外:被测对象就是本 API 的板级用例(P1-06 寄存器断言 / Phase 12 L1
+ * 时延基准,硬件层独占上下文、无并发消费者)。
*/
uint16_t ch395f_get_glob_int_status_all(void);
@@ -748,8 +754,8 @@ void ch395f_set_ttl(uint8_t sock, uint8_t ttl);
void ch395f_get_unreach_info(uint8_t *p_buf);
/*
- * 函数声明区 - N34 阶段 2 驱动原语(实施方案 docs/网络/N34-自愈审计.md §5.4;
- * 形态定案 docs/网络/N34-自愈审计.md §6 E2/E3/E4)
+ * 函数声明区 - 自愈收敛阶段 2 驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4;
+ * 形态定案锚点 同文档 §5.2 E2/E3/E4)
* ⚠ 阶段 2 = 只加不接线:net_poll 全量刷新、三处两级预清改调用、链路 feed 接线
* 均在阶段 3 收敛时进行。E2 实测:单次状态回读 18.1us;LISTEN 可见延迟 100% <=10ms。
*/
diff --git a/Drivers/BSP/NET/README.md b/Drivers/BSP/NET/README.md
index 27eb1789..929a554a 100644
--- a/Drivers/BSP/NET/README.md
+++ b/Drivers/BSP/NET/README.md
@@ -20,9 +20,9 @@
- 一期(RECV)已落地:`net_recv` 等支持带超时的阻塞(`pending` 挂起 + tick 唤醒)
- 二期(ACCEPT + SEND)待做 → `docs/任务规划/登记簿.md` N-4
-### 周期对账自愈(TRAP-NET-17 修复;N34 阶段 3 合并 pump)
+### 周期对账自愈(TRAP-NET-17 修复;自愈收敛阶段 3 合并 pump)
-- `net_reconcile_tick()`(100ms,N34 阶段 3 起判据读**驱动状态缓存**——唯一
+- `net_reconcile_tick()`(100ms,自愈收敛阶段 3 起判据读**驱动状态缓存**——唯一
刷新点 `net_poll` 每轮全量刷新,E2 实测单次回读 18.1us)修正失同步:
本层 ESTABLISHED 而芯片已断 → 断开;本层 LISTENING 而芯片已握手 → 补 accept;
本层 LISTENING/CLOSED(监听型 fd)而芯片监听消失 → 重建(原 pump③④);
diff --git a/Drivers/BSP/NET/lftpd/lftpd.c b/Drivers/BSP/NET/lftpd/lftpd.c
index 358edf4b..964a9e1b 100644
--- a/Drivers/BSP/NET/lftpd/lftpd.c
+++ b/Drivers/BSP/NET/lftpd/lftpd.c
@@ -467,7 +467,7 @@ static int cmd_pasv(lftpd_client_t *client, const char *arg)
client->data_socket = -1;
}
- /* N34 阶段1-6:原 3×200ms 应用层重试已删(lftpd_inet_listen 单次化)——
+ /* 自愈收敛阶段1-6:原 3×200ms 应用层重试已删(lftpd_inet_listen 单次化)——
* 失败直接 425,FTP 客户端自带传输重试语义;服务端不再叠阻塞重试 */
int listener_socket = lftpd_inet_listen(0);
if (listener_socket < 0) {
@@ -795,7 +795,7 @@ int lftpd_start(const char *directory, int port, lftpd_t *lftpd)
{
struct net_sockaddr_in addr;
int addrlen = sizeof(addr);
- /* N34 E13 §6 修复:原用 getsockname 打印的是**本机** IP:21(且
+ /* E13 定案 修复:原用 getsockname 打印的是**本机** IP:21(且
* ntoa 段序反转成 "100.1.168.192")——改 getpeername 打印真实
* 对端(现场排障需要"谁连进来");未连接则退回无地址形态 */
if (net_getpeername(client_socket,
diff --git a/Drivers/BSP/NET/lftpd/lftpd_inet.c b/Drivers/BSP/NET/lftpd/lftpd_inet.c
index 85099fb6..4492eb2f 100644
--- a/Drivers/BSP/NET/lftpd/lftpd_inet.c
+++ b/Drivers/BSP/NET/lftpd/lftpd_inet.c
@@ -34,7 +34,7 @@ void lftpd_task_wd_beat(void)
int lftpd_inet_listen(int port)
{
- /* N34 阶段1-6:应用层阻塞重试退役(原 5 轮 500ms/2000ms,最坏 10s 阻塞
+ /* 自愈收敛阶段1-6:应用层阻塞重试退役(原 5 轮 500ms/2000ms,最坏 10s 阻塞
* ftpTask)——TRAP-NET-09 的启动形态已由就绪屏障消除(FTP_READY 等 NET_READY,
* lftpd_start 外层本就有监听失败重建循环);芯片忙窗失败属罕见,PASV 数据
* 监听交给 FTP 客户端自身的重试语义(主流 FTP 服务器 passive listen 失败
@@ -108,7 +108,7 @@ int lftpd_inet_accept_timeout(int socket, int timeout_ms)
if (s >= 0) {
return s;
}
- /* F-NET-15 M2(N34 实验 2026-10-12):镜像门控退役——原直读 p_sock->state 在
+ /* F-NET-15 M2(自愈专项实验 2026-10-12):镜像门控退役——原直读 p_sock->state 在
* relisten 忙期(CLOSED 瞬态)误判"已关闭",扔掉 ~4.9s 等待预算;armed 门控
* 认"监听器在服役(LISTENING)或待重建且 RX 有残留(closed-with-data 救回窗)",
* 与 :7003 的 N-32 P1 同型修复 */
diff --git a/Drivers/BSP/NET/net_config.h b/Drivers/BSP/NET/net_config.h
index 0570aad2..a6894723 100644
--- a/Drivers/BSP/NET/net_config.h
+++ b/Drivers/BSP/NET/net_config.h
@@ -11,7 +11,7 @@
* 实际生效值 60s/5s/3 并在 net_socket.c 引用(原宏为无效遗留值)
* 2026-09-22 新增 NET_RETRANS_COUNT/NET_RETRANS_PERIOD_MS(P0-a:重传周期
* 500→200ms 治 D1 症状),net_socket.c 的字面量 8/500 移入本文件
- * 2026-10-13 N34 阶段 1.5:删除 NET_RECV_TIMEOUT_MS(30s 默认预算)——
+ * 2026-10-13 自愈收敛阶段 1.5:删除 NET_RECV_TIMEOUT_MS(30s 默认预算)——
* recv 超时改为调用方策略(net_recv 无限阻塞 /
* net_recv_to 显式预算 / net_select 首选),API 不再强加默认值
*/
@@ -33,7 +33,7 @@ extern "C" {
/*
* 超时时间配置(毫秒)
* 0 = 无限等待(阻塞模式)
- * 注:recv 侧无编译期默认预算(N34 阶段 1.5 删除 NET_RECV_TIMEOUT_MS)——
+ * 注:recv 侧无编译期默认预算(自愈收敛阶段 1.5 删除 NET_RECV_TIMEOUT_MS)——
* 超时是调用方策略:net_recv() 无限阻塞、net_recv_to() 显式预算、
* net_select() 首选机制。
*/
diff --git a/Drivers/BSP/NET/net_listener.c b/Drivers/BSP/NET/net_listener.c
index ddc43684..0e6023ff 100644
--- a/Drivers/BSP/NET/net_listener.c
+++ b/Drivers/BSP/NET/net_listener.c
@@ -2,14 +2,14 @@
* 模块名称:Network Listener Lifecycle
* 模块功能:监听器生命周期(真机专属)—— 维持 fd 处于 LISTEN 态的全部机制:
* 重建(open+listen)、重建前的沉降/隔离(TRAP-NET-18)、排空 RX 残渣、
- * D10 非阻塞重试(周期性重建决策者=N34 阶段 3 合并后的
+ * D10 非阻塞重试(周期性重建决策者=自愈收敛阶段 3 合并后的
* net_reconcile_tick,见 net_socket.c)。
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建锋
* 创建日期:2026-10-12
* 修改记录:
* 2026-10-12 王建锋 从 net_socket.c 整体抽出监听生命周期(N-32 §10 模块化)
- * 2026-10-13 王建锋 N34 阶段 3:pump 并入 net_reconcile_tick(实施方案 §5 项 1)
+ * 2026-10-13 王建锋 自愈收敛阶段 3:pump 并入 net_reconcile_tick(《网络自愈架构设计》§4.1)
*
* 职责与边界见 net_listener.h 头注释;设计依据见
* docs/子系统设计/网络Socket监听生命周期设计说明.md §10。
@@ -176,7 +176,7 @@ static int net_relisten_locked(int sockfd) {
ch395f_close_socket((uint8_t)sockfd);
/* 读即清:本 Socket 中断状态寄存器 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
- /* F-NET-15 M4-lite(N34 实验 2026-10-12):删除沉降段的全局读即清——
+ /* F-NET-15 M4-lite(自愈专项实验 2026-10-12):删除沉降段的全局读即清——
* ① 违反模块约定「所有 CH395F 中断在 net_poll 内集中读取并清除」;
* ② 全局寄存器整体读即清无按位手段(B3 实证),每 30ms 重试一轮即搁浅
* 其它在用 Socket 的 CONNECT/DISCONNECT/RECV_OK(net_poll 门控焊死);
@@ -228,11 +228,18 @@ static int net_relisten_locked(int sockfd) {
/* 交重试轮询(30ms 后复检) */
return -1;
}
+ /* 排事件必须在 LISTEN 命令**之前**(E14 复核 2026-10-14:listen 后读清会把
+ * 快速复连新连接的 CONNECT 吞掉——与 net_listen_locked 同源同改法;B3 机制
+ * 与 git 考古见该处注释)。本处是每次断连 auto-relisten 的热路径:只清本
+ * Socket,全局交 net_poll 集中读清(AGENTS 模块约定)。
+ * 读即清:防上一条会话残留 DISCONNECT/TIMEOUT 误触发;挪后若仍有滞后事件
+ * 落进新会话,事件处理器回读状态裁决(状态面为真相),无害 */
+ (void)ch395f_get_sock_int_status((uint8_t)sockfd);
/* 下发 TCP LISTEN 命令 */
status = ch395f_tcp_listen((uint8_t)sockfd);
/* LISTEN 失败 → BUSY(同样交重试) */
if (status != CH395F_ERR_SUCCESS) {
- /* 0-3(N34 阶段0,U12 对称):open 已成而 LISTEN 失败——不 close 会把
+ /* 0-3(自愈收敛阶段0,U12 对称):open 已成而 LISTEN 失败——不 close 会把
* 芯片 Socket 卡在"OPEN 无监听",下一轮 open 返 USE 又进失败分支
* (U12 同源事故形态)。close 交重试轮,与 open 失败分支同形 */
ch395f_close_socket((uint8_t)sockfd);
@@ -251,7 +258,7 @@ static int net_relisten_locked(int sockfd) {
p_sock->released_abnormal = 0U;
/* N-30(TRAP-NET-20):清零前把对端端口转登为幽灵甄别基准 */
p_sock->last_peer_port = p_sock->remote_port;
- /* N-30:新监听会话,丢中断去抖计数清零 */
+ /* N-30:新监听会话,事件未收到去抖计数清零 */
p_sock->lost_conn_seen = 0U;
/* 清对端端口(新连接待用) */
p_sock->remote_port = 0;
@@ -265,13 +272,6 @@ static int net_relisten_locked(int sockfd) {
p_sock->recv_bytes = 0;
/* auto_relisten 保持为 1,下次断连仍自动重监听 */
- /* 读即清:仅本 Socket 的中断状态(防上一条会话残留误触发)。
- * 原为"全 8 个 Socket + 全局"读清,与 net_listen_locked 同源(B3,机制
- * 与 git 考古见该处注释):本处是每次断连 auto-relisten 的热路径,全量清
- * 会持续吞掉其它在用 Socket 的 CONNECT/DISCONNECT 等边沿事件。全局中断
- * 寄存器交 net_poll 集中读清(AGENTS 模块约定)。 */
- (void)ch395f_get_sock_int_status((uint8_t)sockfd);
-
/* 留痕:监听器重建完成 */
DBG_INFO("sock%d auto-relisten ok", sockfd);
/* 重监听成功 */
@@ -415,7 +415,7 @@ void net_listener_retry_tick(void) {
if (s_relisten_wait[i] == 0U) {
continue;
}
- /* 0-4(N34 阶段0)状态门:仅本层仍 LISTENING/CLOSED 才重试。若已转
+ /* 0-4(自愈收敛阶段0)状态门:仅本层仍 LISTENING/CLOSED 才重试。若已转
* ESTABLISHED(新会话抢先到达),在途重试登记即陈旧——继续跑会
* open→close 摧毁活连接(认领口径与 reconciler 形态③④ 一致) */
{
@@ -460,7 +460,7 @@ void net_listener_retry_tick(void) {
}
}
-/* N34 阶段 3(实施方案 §5 项 1):原"监听器 pump"(100ms 周期、去抖/冷却、
+/* 自愈收敛阶段 3(《网络自愈架构设计》§4.1):原"监听器 pump"(100ms 周期、去抖/冷却、
* 情形①②③)已并入 net_socket.c 的 net_reconcile_tick 单一 100ms tick
* (判据读驱动状态缓存);本模块保留沉降/隔离、D10 重试、armed 查询与
* net_listener_try(重建动作本身,由 reconciler 形态③④ 调用)。 */
diff --git a/Drivers/BSP/NET/net_listener.h b/Drivers/BSP/NET/net_listener.h
index 429e8100..c6a6f849 100644
--- a/Drivers/BSP/NET/net_listener.h
+++ b/Drivers/BSP/NET/net_listener.h
@@ -2,7 +2,7 @@
* 模块名称:Network Listener Lifecycle (Header)
* 模块功能:监听器生命周期模块的对外接口(**真机专属**)—— 维持 fd 处于
* LISTEN 态的全部机制:重建、重建前的沉降/隔离(TRAP-NET-18)、排空 RX
- * 残渣、D10 非阻塞重试(周期重建决策者=N34 阶段 3 合并后的
+ * 残渣、D10 非阻塞重试(周期重建决策者=自愈收敛阶段 3 合并后的
* net_reconcile_tick,见 net_socket.c)。
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建锋
@@ -86,8 +86,8 @@ void net_listener_clear(int sockfd);
*/
void net_listener_retry_tick(void);
-/* N34 阶段 3:原 `net_listener_pump`(周期重建决策者)已并入 net_socket.c 的
- * net_reconcile_tick 单一 100ms tick(判据读驱动状态缓存,实施方案 §5 项 1)。
+/* 自愈收敛阶段 3:原 `net_listener_pump`(周期重建决策者)已并入 net_socket.c 的
+ * net_reconcile_tick 单一 100ms tick(判据读驱动状态缓存,《网络自愈架构设计》§4.1)。
* 本模块的 net_listener_try 由 reconciler 形态③④ 与 D10 重试轮询调用。 */
/* 注:本模块消费的宿主能力 `net_pending_active()` 声明在 net_socket.h
diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c
index da878edb..c58aeffc 100644
--- a/Drivers/BSP/NET/net_socket.c
+++ b/Drivers/BSP/NET/net_socket.c
@@ -39,13 +39,13 @@
*/
#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔(ms) */
-/* N34 E6 实验插桩(U15 失配频率,docs/网络/N34-自愈审计.md §6 E6):
- * 假设=「RX 非空但 readable=0」(RECV_OK 中断丢失的数据面形态)在生产真实
+/* E6 实验插桩(U15 失配频率,docs/网络/网络自愈架构设计.md §4.8;裁决锚点 §5.2 E6):
+ * 假设=「RX 非空但 readable=0」(RECV_OK 事件未收到的数据面形态)在生产真实
* 发生。置 1 烧实验固件:net_poll 每 32 轮对在用 fd 发一条 GET_RECV_LEN,
* 命中计数 + 低频日志(每小时 ≤1 条防刷屏);跑 24h 常态 + E1 压测。
* 判据:命中 >0 → 阶段 3 项 4(U15 内化)立项;=0(含压测)→ 上层 32 轮
* 盲收保留现状、U15 登记观察项。**默认 0 = 产品构建零成本(编译剔除)**。
- * E6 已裁决(2026-10-13,实验方案 E6 结果):命中=0(含压测)→ 项 4 不立项,维持默认 0。
+ * E6 已裁决(2026-10-13,《网络自愈架构设计》§5.2 E6):命中=0(含压测)→ 项 4 不立项,维持默认 0。
* ⚠ 必须定义在文件最前部:bring_up 的 armed 日志与 net_poll 探测环两处消费。 */
#define NET_U15_PROBE 0
#if NET_U15_PROBE
@@ -196,7 +196,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
* 按 ①数据 ②到点(仅显式预算) ③断连 顺序检查完成条件并通知调用方(语义逐项对齐旧自旋循环)
* 关闭/网络重建/陈旧重同步走显式 settle/cancel 路径(取消必通知,调用方永不挂死)
* 公开 API 返回契约不变:>0 字节数 / 0 EOF / -1 错误(错误码经通知值回传)
- * N34 阶段 1.5:预算归调用方——timeout_ms=0 即无限等待(30s 默认预算已删),
+ * 自愈收敛阶段 1.5:预算归调用方——timeout_ms=0 即无限等待(30s 默认预算已删),
* 原"到点 +1s 强制了结"的看门狗降级为 tripwire(只留痕、绝不伪造返回值)
*/
#define NET_PENDING_REGISTERED 0x70000000 /* 内部哨兵:pending 已登记(分派跳过即时通知;取值不与字节数空间冲突) */
@@ -204,7 +204,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
typedef struct {
uint8_t active; /* 1 = 本 fd 有挂起 recv */
- uint8_t infinite; /* 1 = 无限等待(无 deadline,N34 阶段 1.5:0 预算 = 永等) */
+ uint8_t infinite; /* 1 = 无限等待(无 deadline,自愈收敛阶段 1.5:0 预算 = 永等) */
uint8_t tripwire_logged; /* 1 = 本轮挂起已打过 tripwire(保证只留痕一条) */
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
int len; /* 缓冲容量 */
@@ -277,7 +277,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
/* 复位芯片 */
ch395f_reset();
- /* 芯片版本留痕 + 0-6 版本门控(N34 阶段0,§5.52 表):bit3 SOCKET_CLOSE
+ /* 芯片版本留痕 + 0-6 版本门控(自愈收敛阶段0,§5.52 表):bit3 SOCKET_CLOSE
* 需芯片 ≥0x46、TCP_SERVER 需 ≥0x44。本台架芯片实测 ≥0x46(TRAP-NET-20 定案
* SOCKET_CLOSE 实际生效);门控防批次漂移——版本不足则**拒置 bit3**
* (退回芯片自动关断语义)并 [ERR] 留痕,不静默带病运行 */
@@ -479,7 +479,7 @@ int net_poll(void) {
/* 读取全局中断状态(2 字节):读操作本身会清除 CH395F 中断并使其拉高 INT#,
* 若此后仍有新中断,CH395F 会再次拉低 INT#,由下一次 net_poll() 处理 */
int_status = ch395f_get_glob_int_status_all();
- /* N34 阶段 3(F-DRV-13 接线):把全局位喂驱动——bit2 PHY_CHANGE 时驱动
+ /* 自愈收敛阶段 3(F-DRV-13 接线):把全局位喂驱动——bit2 PHY_CHANGE 时驱动
* 重读 PHY 更新 link_up 缓存 + 变化计数,net 层在 reconciler 订阅留痕
* (运行期拔网线不再静默;不自动重配,重配仍走 UI/配置引擎路径) */
ch395f_glob_event_feed(int_status);
@@ -561,7 +561,7 @@ int net_poll(void) {
/* 因此每次 net_poll() 只处理“一批”,剩余数据在后续轮询中读取 */
} while (0);
- /* N34 阶段 3(§5 项 1 基础):每轮全量刷新在用 Socket 的驱动状态缓存
+ /* 自愈收敛阶段 3(§5 项 1 基础):每轮全量刷新在用 Socket 的驱动状态缓存
* (唯一刷新点;E2 实测单次回读 18.1us,在用 5~6 个 ≈100us ≪ 1ms 轮询
* 周期)。reconciler / 探针 / 镜像降级决策点一律读缓存(
* ch395f_sock_state_cached),不再各自发状态 SPI。放在中断处理之后:
@@ -569,7 +569,7 @@ int net_poll(void) {
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_net_socks[i].in_use != 0U) {
(void)ch395f_get_sock_state((uint8_t)i);
- /* N34 阶段 3(E-02 收口):驱动 sock_reset 状态机在 net_poll 步进
+ /* 自愈收敛阶段 3(E-02 收口):驱动 sock_reset 状态机在 net_poll 步进
* (驱动头注释契约"在 net_poll 或测试循环里周期调用")。IDLE 槽位
* 纯 RAM 判返回 0 无 SPI;仅活跃复位机做短 SPI。作用:同步包装
* (net_sock_reset_sync)600ms 放弃后复位机不搁浅,芯片条件满足
@@ -579,9 +579,9 @@ int net_poll(void) {
}
#if NET_U15_PROBE
- /* E6 探测(宏说明见文件前部):RX 非空而 readable=0 = RECV_OK 中断丢失
+ /* E6 探测(宏说明见文件前部):RX 非空而 readable=0 = RECV_OK 事件未收到
* 的数据面形态。**两级确认去良性竞态**:数据刚到、本轮中断尚未读取的
- * 竞态窗(≤1 个 poll)会在下一探测轮自然消失;丢中断则持续存在直到
+ * 竞态窗(≤1 个 poll)会在下一探测轮自然消失;事件未收到则持续存在直到
* 盲收/超时。候选轮标记 → 下一探测轮仍成立才计确认命中。排除 CLOSED:
* closed-with-data 是已知被救回形态,非 U15 假设对象。独立于上面的
* 缓存刷新环:GET_RECV_LEN 是独立 SPI。 */
@@ -621,23 +621,22 @@ int net_poll(void) {
return event_count;
}
-/* 对账+pump 合并周期(N34 阶段 3:原 200ms 对账 / 100ms pump 合一) */
+/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */
#define NET_RECONCILE_PERIOD_MS 100U
/* 同一 socket 两次动手的最小间隔(防抖成风暴) */
#define NET_RECONCILE_COOLDOWN_MS 1000U
/* 分级去抖门槛(单位=轮) */
-#define NET_RECON_STABLE_ACCEPT 1U /* 形态② 丢 CONNECT 补 accept(幽灵甄别兜底假阳性) */
+#define NET_RECON_STABLE_ACCEPT 1U /* 形态② CONNECT 事件未收到补 accept(幽灵甄别兜底假阳性) */
#define NET_RECON_STABLE_RELISTEN 2U /* 形态③④ 监听失位重建 / ⑤ 静默回收 RESYNC */
#define NET_RECON_STABLE_DEATH 4U /* 形态① 连接已失(M3 让位窗 400ms,勿改小) */
/*
- * 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 丢中断自愈)+ 监听器 pump
- * (N-32 P2)—— N34 阶段 3 合并为单一 100ms tick(docs/网络/N34-自愈审计.md §5.5 项 1)
+ * 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
- * 函数说明:1. 芯片快速复连会丢 CONNECT/DISCONNECT 中断,本层无法靠事件自愈
+ * 函数说明:1. 芯片快速复连会收不到 CONNECT/DISCONNECT 事件(读清消费竞态,E14 复核:非芯片丢事件),本层无法靠事件自愈
* 2. 判据读**驱动状态缓存**(唯一刷新点=net_poll 每轮全量刷新,E2 实测单次回读 18.1us)
* 3. 覆盖形态(原对账①②⑤ + 原 pump③④/U11):
* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实)
@@ -702,7 +701,7 @@ static void net_reconcile_tick(void) {
int listener_fd = ((p_sock->auto_relisten != 0U) &&
(p_sock->type == NET_SOCK_STREAM) &&
(p_sock->local_port != 0U));
- /* 失同步类型:1=连接已失 2=握手已成丢中断 3=监听失位 4=孤儿监听
+ /* 失同步类型:1=连接已失 2=握手已成事件未收到 3=监听失位 4=孤儿监听
* 5=芯片静默回收回监听(编号沿用历史口径,U11 重同步不设编号——被动零动作) */
int bad = 0;
/* 本形态去抖门槛(默认按重建档) */
@@ -720,14 +719,14 @@ static void net_reconcile_tick(void) {
stable_need = NET_RECON_STABLE_DEATH;
} else if ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN)) {
- /* ⑤(F-NET-2 缺口,N34 阶段1):芯片静默回收——本层 ESTABLISHED
+ /* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED
* 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT
* 中断**,run8 台架实证)→ RESYNC 语义 */
bad = 5;
}
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
if (c.tcp_state == CH395F_TCP_ESTABLISHED) {
- /* ② 本层 LISTENING 而芯片已完成握手(丢 CONNECT 中断)→ 补做 accept
+ /* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept
* N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内芯片假报 ESTABLISHED,
* 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给
* 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。
@@ -736,7 +735,7 @@ static void net_reconcile_tick(void) {
ch395f_get_remot_ipp((uint8_t)i, remote);
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 仅"端口非零且 = 上一条连接端口"判幽灵拦截;peer==0 不拦截
- * (对端端口提交滞后时不得挡掉丢中断救援,见 net_lost_connect_probe 注释) */
+ * (对端端口提交滞后时不得挡掉事件未收口的救援,见 net_lost_connect_probe 注释) */
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2;
stable_need = NET_RECON_STABLE_ACCEPT;
@@ -796,7 +795,7 @@ static void net_reconcile_tick(void) {
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
- * 原样并入,N34 阶段1;纪律同 U11:**不发任何芯片命令**——
+ * 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**——
* 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒,
* run8 实证 170s 拒绝) */
DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync",
@@ -838,8 +837,8 @@ static void net_reconcile_tick(void) {
* 2. net_listener_retry_tick(D10):断连后按 30ms(NET_RELISTEN_RETRY_MS)
* 节拍重试 open+listen,2s 总时限到期放弃留痕;本 fd 有在途挂起 recv 时跳过(L3 保险)
* 3. net_pending_tick(L3):按节拍了结挂起 recv(数据/到点[仅显式预算]/断连);
- * L3 tripwire(N34 阶段 1.5):挂起超 60s 只 DBG_ERROR 留痕一次,不再强制了结/伪造返回值
- * 4. net_reconcile_tick(TRAP-NET-17 + pump 合并,N34 阶段 3):每 100ms 对账
+ * L3 tripwire(自愈收敛阶段 1.5):挂起超 60s 只 DBG_ERROR 留痕一次,不再强制了结/伪造返回值
+ * 4. net_reconcile_tick(TRAP-NET-17 + pump 合并,自愈收敛阶段 3):每 100ms 对账
* ①②⑤ + ③④ 重建 + U11 重同步,判据读驱动状态缓存(net_poll 每轮
* 全量刷新,E2 背书);去抖分级(②=1 轮/③④⑤=2 轮/①=4 轮,M3 让位窗)
* + 1s 冷却;重建失败进 D10 重试队列,reconciler 不重复催
@@ -861,7 +860,7 @@ void net_process_messages(void) {
net_listener_retry_tick();
/* L3 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
net_pending_tick();
- /* TRAP-NET-17 状态对账 + 监听器 pump(N34 阶段 3 合并单一 100ms tick):
+ /* TRAP-NET-17 状态对账 + 监听器 pump(自愈收敛阶段 3 合并单一 100ms tick):
* 判据读驱动缓存(net_poll 每轮全量刷新),①②⑤ 失同步 + ③④ 重建 +
* U11 重同步全部在此(本层唯一周期重建入口,N-32 P2 单一所有者) */
net_reconcile_tick();
@@ -952,7 +951,7 @@ void net_process_messages(void) {
break;
}
case NET_MSG_DROP_CONN: {
- /* N34 阶段 3(E-02 收口):应用放弃会话的主动拆除(拆连接、保监听) */
+ /* 自愈收敛阶段 3(E-02 收口):应用放弃会话的主动拆除(拆连接、保监听) */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF */
@@ -971,7 +970,7 @@ void net_process_messages(void) {
}
break;
}
- /* N34 阶段1(F-NET-3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
+ /* 自愈收敛阶段1(F-NET-3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
* 手动自愈消息已删——RESYNC 语义并入对账⑤(net_reconcile_tick),
* RELISTEN 与 reconciler/D10 完全重叠且对健康监听器是 footgun;
* 生产调用者本就为零(仅 test/board Phase 13,已改由自动机制接管) */
@@ -1095,7 +1094,7 @@ int net_socket(int domain, int type) {
p_sock->send_ready = 1;
/* 无可读数据 */
p_sock->readable = 0;
- /* N34 阶段 1.5:阻塞模式默认 = 阻塞(BSD/Python 语义;非阻塞用
+ /* 自愈收敛阶段 1.5:阻塞模式默认 = 阻塞(BSD/Python 语义;非阻塞用
* net_setblocking(fd, 0) 显式设置)。close 的 memset 会清零本字段,
* 新 fd 必须在此显式置 1(同 released_abnormal 的"不得继承"纪律) */
p_sock->blocking = 1;
@@ -1222,7 +1221,7 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
}
/*
- * 函数功能:驱动 sock_reset 原语的有界确定性排渣等待(P2b 收口,N34 阶段 3;
+ * 函数功能:驱动 sock_reset 原语的有界确定性排渣等待(P2b 收口,自愈收敛阶段 3;
* 替代原"close + 盲等 500ms"式忙等)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* mode - CH395F_RESET_GRACEFUL(等 100ms)/ QUARANTINE(等 400ms)
@@ -1298,7 +1297,7 @@ int net_listen_locked(int sockfd) {
ch395f_set_sour_port(sockfd, p_sock->local_port);
/* CH395F 关闭 Socket 后需延时才能重新打开(TRAP-NET-09),故失败重试最多 3 次。
- * P2b 收口(N34 阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
+ * P2b 收口(自愈收敛阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
* sock_reset(读空 RX -> CLOSE -> 排空事件 -> 等 byte0=00H,内置 LISTEN
* 态二段 CLOSE 补发;E4 定案 60 轮实证复位后 open 必成)。GRACEFUL
* 最短等 100ms ≪ 原 500ms 盲等;复位失败/超时仍走下一轮重试,TRAP-NET-09 语义保留 */
@@ -1316,24 +1315,14 @@ int net_listen_locked(int sockfd) {
if (status != CH395F_ERR_SUCCESS) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
- /* 下发 TCP LISTEN 命令,进入监听状态 */
- status = ch395f_tcp_listen(sockfd);
- /* LISTEN 失败 → BUSY */
- if (status != CH395F_ERR_SUCCESS) {
- /* 0-3(N34 阶段0,U12 对称):open 已成而 LISTEN 失败必须 close,
- * 否则芯片 Socket 卡"OPEN 无监听"、后续 open 恒返 USE(同
- * net_relisten_locked 对应分支) */
- ch395f_close_socket(sockfd);
- return NET_ERR_RETURN_1(NET_ERR_BUSY);
- }
-
- /* 监听 Socket 进入 LISTENING 态 */
- p_sock->state = NET_SOCK_STATE_LISTENING;
- /* 单连接模式:连接断开后由 net 层自动回到 LISTEN(模拟 BSD 监听常驻) */
- p_sock->auto_relisten = 1;
- /* N-30(TRAP-NET-20):新监听会话,丢中断去抖计数清零 */
- p_sock->lost_conn_seen = 0U;
- /* 读即清:仅本 Socket 的中断状态——丢弃上一条会话残留的 DISCONNECT/
+ /* 排事件(读即清)必须在 LISTEN 命令**之前**(E14 复核实验 2026-10-14:
+ * 原形态为 listen 后读清——快速复连时新连接的 CONNECT 在 LISTEN 生效后、
+ * 读清前落进 SINT,被本方读清吞掉,即"CONNECT 事件未收到→数据被吞"的
+ * 生产侧自伤机制(台架矩阵 3/48 被 drain-int 探针现场抓获);芯片侧
+ * 从不丢事件。挪前后若仍有滞后 DISCONNECT 落进新会话,
+ * handle_disconnect_event 的 LISTENING 分支回读状态裁决(状态面为真相),
+ * 无害。
+ * 读即清:仅本 Socket 的中断状态——丢弃上一条会话残留的 DISCONNECT/
* TIMEOUT,避免监听刚开始就误触发事件。
* ⚠ B3(2026-10-12 审计):此处原为"遍历 8 个 Socket 全清 + 再读清全局",
* git 考古证明是多连接时代的调试遗留(e862009bc 无注释引入 → 319d00e3
@@ -1350,6 +1339,24 @@ int net_listen_locked(int sockfd) {
* 本次未动。 */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
+ /* 下发 TCP LISTEN 命令,进入监听状态 */
+ status = ch395f_tcp_listen(sockfd);
+ /* LISTEN 失败 → BUSY */
+ if (status != CH395F_ERR_SUCCESS) {
+ /* 0-3(自愈收敛阶段0,U12 对称):open 已成而 LISTEN 失败必须 close,
+ * 否则芯片 Socket 卡"OPEN 无监听"、后续 open 恒返 USE(同
+ * net_relisten_locked 对应分支) */
+ ch395f_close_socket(sockfd);
+ return NET_ERR_RETURN_1(NET_ERR_BUSY);
+ }
+
+ /* 监听 Socket 进入 LISTENING 态 */
+ p_sock->state = NET_SOCK_STATE_LISTENING;
+ /* 单连接模式:连接断开后由 net 层自动回到 LISTEN(模拟 BSD 监听常驻) */
+ p_sock->auto_relisten = 1;
+ /* N-30(TRAP-NET-20):新监听会话,事件未收到去抖计数清零 */
+ p_sock->lost_conn_seen = 0U;
+
/* 监听建立完成 */
return 0;
}
@@ -1414,27 +1421,29 @@ int net_listen(int sockfd) {
* 限定条件:Socket 处于 LISTENING 状态
* 函数说明:单连接:监听 Socket 自身在 CONNECT 后已转为 ESTABLISHED
*/
-/* ===== N-30 accept 路径"丢 CONNECT 中断"补 accept(2026-10-11 裸机芯片台架实证)=====
+/* ===== N-30 accept 路径"CONNECT 事件未收到补 accept"(2026-10-11 台架实证;
+ * E14 复核 2026-10-14:事件非芯片侧丢失,是被读清消费——机制不变)=====
* 台架结论(剥离 OS/net/FTP 直接对芯片做受控 close→open→listen→connect 矩阵):
* 芯片关闭连接后的拆链收尾窗口**变长且不可见**(状态寄存器显示 CLOSED 也不代表
* 拆完,实测 >2s 仍可能未拆完);该窗口内重新 listen 后,新连接握手照常完成
- * (芯片状态寄存器 → ESTABLISHED)但 **CONNECT 中断被丢**——纯事件驱动的 accept
+ * (芯片状态寄存器 → ESTABLISHED)但 **CONNECT 事件未收到**(E14 复核:被读清
+ * 消费竞态吞掉)——纯事件驱动的 accept
* 永远等不到 → 数据被吞 / 425。固定沉降延时、Socket 轮换、listen 后 close 重试、
* 缓冲重分配均已被实验证伪;唯一有效 = "中断 + 状态轮询"双信源(修复组 15/15
* 全过,机制触发 5 次全部救回)。→ CH395F_Trap_Records.md TRAP-NET-20
* 幽灵态甄别:拆链窗口内芯片也会直接假报 ESTABLISHED(旧连接未拆净),此时回读
* 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept,
- * 等旧连接拆净 / 新 SYN 到达(新连接若也丢中断,后续轮询以新端口命中)。 */
+ * 等旧连接拆净 / 新 SYN 到达(新连接若事件也未收到,后续轮询以新端口命中)。 */
/*
- * 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 中断
- * 未到(丢中断)时补做 accept(复用 CONNECT 事件簿记路径)。
+ * 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 事件
+ * 未收到时补做 accept(复用 CONNECT 事件簿记路径)。
* 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t *
* 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED)
* 返回值:无
* 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(),
* 驱动缓存新鲜度有保证)
- * 函数说明:1. N34 阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一
+ * 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一
* 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已
* 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED
* 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED
@@ -1458,16 +1467,16 @@ static void net_lost_connect_probe(net_sock_t *p_sock) {
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截;
* peer==0(芯片对端端口提交滞后,TRAP-NET-13 同类)不拦截——宁可补 accept
- * 也不退回丢中断死等(2026-10-11 验证轮实测:peer==0 拦截把首连接
- * 丢中断的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */
+ * 也不退回事件未收口的死等(2026-10-11 验证轮实测:peer==0 拦截把首连接
+ * 事件未收到的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */
if ((peer != 0U) && (peer == p_sock->last_peer_port)) {
p_sock->lost_conn_seen = 0U;
return;
}
- /* N34 阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除,
+ /* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除,
* 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */
p_sock->lost_conn_seen = 0U;
- /* 留痕:丢中断自愈命中 */
+ /* 留痕:事件未收到自愈命中 */
DBG_INFO("sock%d lost-CONNECT self-accept (peer=%u)",
(int)p_sock->ch395_sock, peer);
/* 复用 CONNECT 事件的簿记路径(转 ESTABLISHED + 记对端 + 事件回调) */
@@ -1482,7 +1491,7 @@ static void net_lost_connect_probe(net_sock_t *p_sock) {
* 返回值:新连接的 Socket 描述符,失败返回 -1 int
* 限定条件:Socket 处于 LISTENING 状态
* 函数说明:1. 单连接:监听 Socket 自身在 CONNECT 后已转为 ESTABLISHED
- * 2. 等待期叠加芯片状态轮询探针(丢 CONNECT 中断自愈,TRAP-NET-20)
+ * 2. 等待期叠加芯片状态轮询探针(CONNECT 事件未收到自愈,TRAP-NET-20)
* 3. CLOSED + 芯片 RX 缓冲有残留数据:接受"已关但可读"连接排空
* (FUN_PARA §5.52 ② 保留现场契约,TRAP-NET-20 三次定案)
*/
@@ -1500,9 +1509,10 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
/* 先轮询一次:把状态机刷新到芯片侧最新状态 */
net_poll();
- /* N-30(TRAP-NET-20):监听等待期兼做芯片状态第二信源——丢 CONNECT 中断时
- * 去抖补 accept(裸机台架实证:拆链窗口丢中断是数据被吞/425 的主根因,
- * 纯事件等待无自愈可能;详见 net_lost_connect_probe 区块注释) */
+ /* N-30(TRAP-NET-20):监听等待期兼做芯片状态第二信源——CONNECT 事件未收到时
+ * 去抖补 accept(台架实证:拆链窗口事件未收到是数据被吞/425 的主根因,
+ * E14 复核:事件非芯片丢失、被读清消费吞掉;纯事件等待无自愈可能;
+ * 详见 net_lost_connect_probe 区块注释) */
if (p_listen_sock->state == NET_SOCK_STATE_LISTENING) {
net_lost_connect_probe(p_listen_sock);
}
@@ -1621,8 +1631,8 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return msg.result;
}
-/* N34 阶段1(F-NET-4):net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
- * 门控会把"监听器在服役但本轮还没 CONNECT"的窗口全挡掉(:2404 丢 CONNECT 探针
+/* 自愈收敛阶段1(F-NET-4):net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
+ * 门控会把"监听器在服役但本轮还没 CONNECT"的窗口全挡掉(:2404 CONNECT 事件补 accept 探针
* 因此从不运行,与已修的 :7003 探针失效同型);唯一生产调用者 iec104 已迁
* net_listener_armed。net_can_read(RECV 门控)不受影响、保留。 */
@@ -1634,9 +1644,9 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
* 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int
* 限定条件:任意任务上下文;net_init() 已调用
* 函数说明:1. N-32(P1 不变量 II):取代已退役的 net_can_accept()(要求
- * ESTABLISHED,N34 阶段1 删除)作为监听 accept 门控。原门控下
+ * ESTABLISHED,自愈收敛阶段1 删除)作为监听 accept 门控。原门控下
* :7003 永不进 net_accept →
- * net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行
+ * net_lost_connect_probe(CONNECT 事件未收到补 accept)对该端口从不运行
* (设计文档 §2.3 末"探针失效")
* 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338)
* 只在 net_accept_locked 内部发生,门控挡在外面就永远到不了;
@@ -1659,13 +1669,13 @@ int net_listener_armed(int sockfd) {
/* 连接已到、应用尚未 accept:单连接模型下 CONNECT 处理(net_poll 分派 /
* 探针补 accept / 对账② standalone accept)把监听 fd 转 ESTABLISHED,
* 这就是"待 accept"电平——**必须放行**(原 net_can_accept 唯一放行态;
- * N34 阶段1 i104 台架实证:200ms 任务周期抢不过对账②的 ~400ms 去抖,
+ * 自愈收敛阶段1 i104 台架实证:200ms 任务周期抢不过对账②的 ~400ms 去抖,
* 对账②先转 ESTABLISHED → 不放行则应用永不 accept、会话整场饿死)。
* 无 RPC 风暴风险:此态下 accept 必返 fd,一次调用即离开本分支 */
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
return 1;
}
- /* 正常监听态:放行。net_lost_connect_probe(丢 CONNECT 补 accept)与常规
+ /* 正常监听态:放行。net_lost_connect_probe(CONNECT 事件未收到补 accept)与常规
* accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
return 1;
@@ -1700,7 +1710,7 @@ int net_listener_armed(int sockfd) {
* 2. 非 ESTABLISHED / 描述符失效一律返回 1:必须让上层真的 recv 一次
* 拿到 EOF/错误并复位自身状态,门掉它会把"连接已不存在"永久藏住
* 3. ⚠ readable 仅由 RECV_OK 中断置位,TRAP-NET-17 对账不含"RX 非空但
- * readable=0" → 中断丢失时本函数恒返 0,调用方须保留低频兜底 recv
+ * readable=0" → 事件未收到时本函数恒返 0,调用方须保留低频兜底 recv
*/
int net_can_read(int sockfd) {
/* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */
@@ -1848,7 +1858,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
/* CH395F 关闭 Socket 后需延时才能重新打开(TRAP-NET-09),故失败重试最多 3 次。
- * P2b 收口(N34 阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
+ * P2b 收口(自愈收敛阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
* sock_reset(读空 RX -> CLOSE -> 排空事件 -> 等 byte0=00H,内置 LISTEN
* 态二段 CLOSE 补发;E4 定案 60 轮实证复位后 open 必成)。GRACEFUL
* 最短等 100ms ≪ 原 500ms 盲等;复位失败/超时仍走下一轮重试,TRAP-NET-09 语义保留 */
@@ -1869,7 +1879,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
}
/* 清掉本 Socket 可能残留的中断状态,丢弃上一条连接遗留的 DISCONNECT。
- * F-NET-7(N34 阶段 3):只清**本 Socket** SINT(驱动护栏原语)——原
+ * F-NET-7(自愈收敛阶段 3):只清**本 Socket** SINT(驱动护栏原语)——原
* "再读清全局"会焊死其它在用 Socket 的事件位(全局寄存器唯一读者=net_poll) */
(void)ch395f_sock_events((uint8_t)sockfd);
@@ -2070,7 +2080,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
- * timeout_ms - 本次等待预算(0 = 无限等待,N34 阶段 1.5 起无默认值)uint32_t
+ * timeout_ms - 本次等待预算(0 = 无限等待,自愈收敛阶段 1.5 起无默认值)uint32_t
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
@@ -2078,7 +2088,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回,行为与旧实现一致
* 2. 非阻塞(NET_MSG_DONTWAIT 单次覆盖 **或** socket blocking 属性为 0,
- * N34 阶段 1.5 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
+ * 自愈收敛阶段 1.5 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. 无数据 + 阻塞等待 → 登记挂起请求(L3 pending 化,E-02 结构演进)
* 4. netTask 零占用立即返回;完成条件(数据/到点[仅显式预算]/断连)由 net_pending_tick 按主循环节拍检查
* 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式了结(取消必通知)
@@ -2095,7 +2105,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
}
/* 超时来源:只有调用方显式预算(net_recv_to);0 = 无限等待
- * (N34 阶段 1.5:NET_RECV_TIMEOUT_MS 30s 默认预算已删,API 不强加隐藏值) */
+ * (自愈收敛阶段 1.5:NET_RECV_TIMEOUT_MS 30s 默认预算已删,API 不强加隐藏值) */
uint32_t to_ms = timeout_ms;
/* 先轮询一次:刷新状态机与可读标志 */
@@ -2119,7 +2129,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
}
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket 的 blocking 属性为 0
- * (net_setblocking(fd, 0),N34 阶段 1.5 模式挂控制块属性) */
+ * (net_setblocking(fd, 0),自愈收敛阶段 1.5 模式挂控制块属性) */
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
@@ -2169,7 +2179,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
* 2. 清空整个控制块(memset 0),释放该 Socket 资源
*/
-/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22;N34 阶段 1.5 语义主流化) =====
+/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22;自愈收敛阶段 1.5 语义主流化) =====
* 完成条件按 ①数据 ②到点(仅显式预算) ③断连 顺序检查(逐项对齐旧自旋循环语义)
* ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径零额外 SPI 事务
* (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位)
@@ -2179,7 +2189,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
* 旧注归"部分读归零"机理、已随复核注降级)→ 有数据按数据完成,否则 EOF(0) + 自动重监听
* 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED 是合法路径;
* 陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀正在建链的等待
- * tripwire(N34 阶段 1.5 降级):挂起超 60s = "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,
+ * tripwire(自愈收敛阶段 1.5 降级):挂起超 60s = "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,
* **绝不强制了结、绝不伪造 TIMEDOUT 返回值**(唤醒责任在 ①②③ 与显式 cancel 路径)
*/
@@ -2222,7 +2232,7 @@ static void net_pending_complete(int sockfd, int result) {
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 调用方接收缓冲(须保持有效至完成) void *
* len - 缓冲容量 int
- * to_ms - 本次等待预算(0 = 无限等待,N34 阶段 1.5) uint32_t
+ * to_ms - 本次等待预算(0 = 无限等待,自愈收敛阶段 1.5) uint32_t
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
* 出口参数:无
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
@@ -2285,7 +2295,7 @@ static int net_pending_register(int sockfd, void *buf, int len,
* 限定条件:netTask 上下文
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(仅显式预算)/ 断连
* 2. 完成即经 net_pending_complete 通知并清除
- * 3. tripwire(N34 阶段 1.5):挂起超 NET_RECV_TRIPWIRE_MS 只 [ERR] 留痕
+ * 3. tripwire(自愈收敛阶段 1.5):挂起超 NET_RECV_TRIPWIRE_MS 只 [ERR] 留痕
* 一次("完成逻辑缺位"探针),绝不强制了结、绝不伪造返回值
*/
static void net_pending_tick(void) {
@@ -2338,7 +2348,7 @@ static void net_pending_tick(void) {
}
}
- /* ② 到点(仅显式预算登记;无限等待登记无 deadline——N34 阶段 1.5:
+ /* ② 到点(仅显式预算登记;无限等待登记无 deadline——自愈收敛阶段 1.5:
* 超时是调用方策略,net_recv_to 的预算在此兑现,不是伪造。
* tick 差值按有符号比较,抗回绕) */
if ((p->infinite == 0U) && ((int32_t)(now - p->deadline) >= 0)) {
@@ -2351,7 +2361,7 @@ static void net_pending_tick(void) {
* 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED ||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
- * (lftpd 阻塞 recv 的"消费者卡死"原型)。N34 阶段 1.5 后该形态更致命:
+ * (lftpd 阻塞 recv 的"消费者卡死"原型)。自愈收敛阶段 1.5 后该形态更致命:
* 无限等待登记没有 ②,漏 ③ = 永久挂死(只剩 tripwire 留痕)——
* ③ 与显式 cancel 是无限 recv 的全部唤醒责任方,勿动判据。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
@@ -2399,7 +2409,7 @@ static void net_pending_tick(void) {
/* 其他状态:保持等待(见本区块头注释) */
- /* ④ tripwire(N34 阶段 1.5 降级,原"看门狗强制了结"):挂起超 60s =
+ /* ④ tripwire(自愈收敛阶段 1.5 降级,原"看门狗强制了结"):挂起超 60s =
* "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,**绝不强制了结、绝不伪造
* TIMEDOUT 返回值**(旧实现到点 +1s 兜底伪造 TIMEDOUT,掩盖完成路径
* 缺陷;新语义下无限等待本就无预算,伪造返回值反而违背调用方意图)。
@@ -2535,7 +2545,7 @@ static void net_pending_settle_close(net_sock_t *p_sock) {
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:必须在 netTask 上下文中调用;p_sock 有效
- * 函数说明:1. P2b 收口(N34 阶段 3):原"close + 500ms 盲等排空循环"改
+ * 函数说明:1. P2b 收口(自愈收敛阶段 3):原"close + 500ms 盲等排空循环"改
* **确定性 sock_reset**(读空 RX -> CLOSE(幂等) -> 排空本 Socket
* 事件(quiescent) -> 等 byte0=00H,内置 LISTEN 态二段 CLOSE 补发
* ——E2 补充发现:一次 CLOSE 只拆监听 byte0 恒 05)
@@ -2582,7 +2592,7 @@ static int net_close_locked(net_sock_t *p_sock) {
* 出口参数:无
* 返回值:0 - 拆除完成(监听型 fd 已重监听或交 D10) int
* 限定条件:netTask 上下文;p_sock 在用且为 TCP 流
- * 函数说明:1. E-02 收口(实验方案 E13 §5):应用放弃的会话若 TCP 仍活
+ * 函数说明:1. E-02 收口(《网络自愈架构设计》§5.2 E13):应用放弃的会话若 TCP 仍活
* (PC 内核自动 ACK keepalive,僵尸判不死),单连接监听被永久
* 占住;应用又不得 net_close 监听 fd(毁监听)——本函数补上
* "拆连接不拆监听"的应用侧形态
@@ -2610,7 +2620,7 @@ static int net_drop_conn_locked(net_sock_t *p_sock) {
/* 会话簿记收口(保留 fd:不 memset、不动 local_port/auto_relisten) */
p_sock->state = NET_SOCK_STATE_CLOSED;
p_sock->readable = 0U;
- /* N-30(TRAP-NET-20):新会话起点,丢中断去抖计数清零 */
+ /* N-30(TRAP-NET-20):新会话起点,事件未收到去抖计数清零 */
p_sock->lost_conn_seen = 0U;
if (p_sock->auto_relisten) {
@@ -2700,7 +2710,7 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
/*
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask;
- * **默认无限阻塞**——N34 阶段 1.5 语义主流化,30s 默认预算已删)
+ * **默认无限阻塞**——自愈收敛阶段 1.5 语义主流化,30s 默认预算已删)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
@@ -2744,7 +2754,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
msg.len = len;
/* 标志:0=按 socket 阻塞属性,NET_MSG_DONTWAIT=单次非阻塞 */
msg.flags = flags;
- /* 0 = 无限等待(N34 阶段 1.5:无默认预算;显式预算走 net_recv_to) */
+ /* 0 = 无限等待(自愈收敛阶段 1.5:无默认预算;显式预算走 net_recv_to) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
@@ -2770,7 +2780,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
}
/*
- * 函数功能:接收数据(非阻塞具名入口——N34 阶段 1.5 把 NET_MSG_DONTWAIT 路径转正,
+ * 函数功能:接收数据(非阻塞具名入口——自愈收敛阶段 1.5 把 NET_MSG_DONTWAIT 路径转正,
* 对齐 BSD recv(MSG_DONTWAIT) / Python recv 非阻塞模式的"名如其义")
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
@@ -2790,7 +2800,7 @@ int net_recv_nb(int sockfd, void *buf, int len) {
/*
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读;
- * N34 阶段 1.5 起为**显式预算**便捷入口,等价 Python settimeout,
+ * 自愈收敛阶段 1.5 起为**显式预算**便捷入口,等价 Python settimeout,
* 不再有隐藏默认值)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf / len - 接收缓冲与容量 void * / int > 0
@@ -2822,7 +2832,7 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
}
/* ---- 3. 分片长度检查:timeout_ms = 0 在 netTask 侧意味着"**无限等待**"
- * (N34 阶段 1.5 删除 30s 默认预算),恰与本接口"有界等待"的存在
+ * (自愈收敛阶段 1.5 删除 30s 默认预算),恰与本接口"有界等待"的存在
* 理由相反,直接拒绝 ---- */
if (timeout_ms == 0U) {
/* 分片长度必须 > 0 */
@@ -2944,7 +2954,7 @@ int net_close(int sockfd) {
* 2. 场景=应用放弃会话(对时 30s 预算耗尽、协议超时);EOF 路径**不要**调
* (net 层 auto_relisten 已接管,见 AGENTS 单连接断开语义)
* 函数说明:1. 单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(keepalive
- * 被 PC 内核自动 ACK 判不死,实验方案 E13 §5);应用不得
+ * 被 PC 内核自动 ACK 判不死,《网络自愈架构设计》§5.2 E13);应用不得
* net_close 监听 fd → 本 API 拆连接不拆监听(E-02 收口)
* 2. netTask 侧执行 sock_reset QUARANTINE + 监听即时重建(见
* net_drop_conn_locked);等待期 netTask 有界占用 ~400-600ms
@@ -3131,7 +3141,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用;有数据可用
* 函数说明:1. 轮询检查芯片接收缓冲(net_poll + get_recv_len),有数据即返回
* 2. UDP 数据报前 8 字节为报文头(源端口+源 IP),据此填充 src_addr
- * 3. 无数据时(N34 阶段 1.5,与 net_recv 同构):DONTWAIT / 非阻塞属性
+ * 3. 无数据时(自愈收敛阶段 1.5,与 net_recv 同构):DONTWAIT / 非阻塞属性
* 立即返回 -1(WOULDBLOCK);阻塞模式**无限等待**(30s 默认预算已删;
* 本接口为轮询自旋实现且无生产调用者,受 IWDG 监控的任务勿用无限形态)
*/
@@ -3158,7 +3168,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
- /* 轮询直到:有数据 / 非阻塞(N34 阶段 1.5:阻塞 = 无限等待,无默认预算) */
+ /* 轮询直到:有数据 / 非阻塞(自愈收敛阶段 1.5:阻塞 = 无限等待,无默认预算) */
while (1) {
/* 轮询一次:更新状态机与可读标志 */
net_poll();
@@ -3211,13 +3221,13 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
}
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket blocking 属性为 0
- * (N34 阶段 1.5,与 net_recv_locked 同判据):无数据立即返回 */
+ * (自愈收敛阶段 1.5,与 net_recv_locked 同判据):无数据立即返回 */
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
/* 置 WOULDBLOCK */
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
- /* 阻塞模式:无数据继续轮询等待(N34 阶段 1.5:无限等待,无默认预算——
+ /* 阻塞模式:无数据继续轮询等待(自愈收敛阶段 1.5:无限等待,无默认预算——
* 旧 NET_RECV_TIMEOUT_MS 30s 兜底已删;唤醒源 = 数据,与 net_recv 的
* 差异是本接口为自旋实现,勿在受 IWDG 监控任务中使用无限形态) */
}
@@ -3324,7 +3334,7 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
}
/*
- * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(N34 阶段 1.5,模式挂控制块属性,
+ * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(自愈收敛阶段 1.5,模式挂控制块属性,
* 对齐 BSD O_NONBLOCK / Python setblocking)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞 int
@@ -3402,7 +3412,7 @@ int net_sock_state(int sockfd) {
return (int)s_net_socks[sockfd].state;
}
-/* N34 阶段1(F-NET-3):手动自愈 API net_resync_listen / net_relisten 已删除——
+/* 自愈收敛阶段1(F-NET-3):手动自愈 API net_resync_listen / net_relisten 已删除——
* RESYNC 语义并入对账⑤(net_reconcile_tick 自动认领"芯片静默回收回 LISTEN"形态,
* 200ms×2 去抖 + 1s 冷却沿用);RELISTEN 与 reconciler/D10 完全重叠且对健康监听器是
* footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。生产调用者本就为零
@@ -3458,7 +3468,7 @@ uint32_t net_inet_addr(const char *cp) {
* 返回值:成功返回 buf 指针;buf 为 NULL 时返回 NULL char *
* 限定条件:buf 非空且长度足够(>= 16 字节)
* 函数说明:1. 先经 NET_NTOHL 转为主机字节序再格式化输出
- * 2. ⚠ 段序(N34 E13 §6 修复):NTOHL 后主机序数值的高字节才是
+ * 2. ⚠ 段序(E13 定案 修复):NTOHL 后主机序数值的高字节才是
* 点分第一段(192.168.1.100 → 0xC0A80164 → "192.168.1.100")。
* 原实现低段在前,显示成字节反转的 "100.1.168.192"
*/
@@ -3612,7 +3622,7 @@ static void handle_connect_event(net_sock_t *p_sock) {
/* §5.56:TCP 服务器须在 CONNECT 后开启 KeepAlive。2026-10-12 曾加后
* 撤回(当时仅证明"物理断链"已被 +2s TIM_OUT 快速覆盖,其余场景
- * 无法注入、未证明);E11(2026-10-13,实验方案.md)用"禁用 PC 网卡"
+ * 无法注入、未证明);E11(2026-10-13,《网络自愈架构设计》)用"禁用 PC 网卡"
* 注入"链路在、对端静默"(NAT/黑洞)补上证据链:生产参数 60/5/3
* 实测 ≈30 s 判死 → TIM_OUT → QUARANTINE → reconciler 重建;对端存活时
* 探测被 ACK、空闲会话不误杀。消费链路现成,按手册位置转正。 */
@@ -3712,11 +3722,11 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
/* 直接返回(中断已读清,不会重复上报) */
return;
}
- /* F-NET-17 M3(N34 实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05)
+ /* F-NET-17 M3(自愈专项实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05)
* 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT
* (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨
* (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账①
- * (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(N34 阶段 3 合并
+ * (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并
* 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却):
* flag==1 旁路本门 → 正常释放;连接真活则租约内
* 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。
@@ -3746,7 +3756,7 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
/* TRAP-NET-18 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态
* 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226;
* 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 TRAP-NET-17 的快速复连时序(:21 @0ms ×50 全通过) */
- /* F-NET-15 M1(N34 实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的
+ /* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的
* 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是
* 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测);
* 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空,
@@ -4003,7 +4013,7 @@ int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen) {
}
/*
- * 函数功能:获取 Socket 对端地址(IP + Port)—— N34 E13 §6 lftpd 对端显示收口
+ * 函数功能:获取 Socket 对端地址(IP + Port)—— E13 定案 lftpd 对端显示收口
* 入口参数:sockfd - socket 描述符 int
* 出口参数:addr - 输出:对端地址 struct net_sockaddr *
* addrlen - 输入输出:地址结构体长度 int *
diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h
index 4ce65987..72158406 100644
--- a/Drivers/BSP/NET/net_socket.h
+++ b/Drivers/BSP/NET/net_socket.h
@@ -37,8 +37,8 @@ typedef enum {
NET_MSG_CONNECT,
NET_MSG_LISTEN,
NET_MSG_ACCEPT,
- NET_MSG_DROP_CONN, /* N34 阶段 3(E-02 收口):主动拆会话、保监听(见 net_drop_conn) */
- /* N34 阶段1(F-NET-2/3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
+ NET_MSG_DROP_CONN, /* 自愈收敛阶段 3(E-02 收口):主动拆会话、保监听(见 net_drop_conn) */
+ /* 自愈收敛阶段1(F-NET-2/3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
* 静默回收由对账⑤自动认领(net_reconcile_tick),重建由 reconciler
* 形态③④/D10 单一
* 所有者负责;手动自愈 API 面退役(生产调用者本就为零) */
@@ -51,7 +51,7 @@ typedef struct {
int len; /* 数据长度/缓冲区大小 */
int flags; /* 接收标志 */
uint32_t timeout_ms; /* 接收超时(入队方向;0 = 无限等待,无默认预算——
- * N34 阶段 1.5 删除 NET_RECV_TIMEOUT_MS 30s 默认) */
+ * 自愈收敛阶段 1.5 删除 NET_RECV_TIMEOUT_MS 30s 默认) */
TaskHandle_t caller; /* 调用方任务句柄 */
int result; /* 操作结果 */
/* 连接参数 */
@@ -154,8 +154,8 @@ int net_listen(int sockfd);
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
-/* N34 阶段1(F-NET-4):net_can_accept 已退役——只认 ESTABLISHED 的旧门控会把
- * "监听器在服役、本轮待 CONNECT"窗口全挡掉(丢 CONNECT 探针因此从不运行);
+/* 自愈收敛阶段1(F-NET-4):net_can_accept 已退役——只认 ESTABLISHED 的旧门控会把
+ * "监听器在服役、本轮待 CONNECT"窗口全挡掉(CONNECT 事件补 accept 探针因此从不运行);
* 监听 accept 门控统一用 net_listener_armed(),recv 门控 net_can_read() 保留。 */
/*
@@ -165,14 +165,14 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
* 返回值:1 - 就绪(ESTABLISHED 待 accept / LISTENING / CLOSED+auto_relisten
* +残留数据);0 - 不就绪 int
* 限定条件:net_init() 已调用;任意任务上下文
- * 函数说明:1. N-32(P1 不变量 II,N34 阶段1 补齐 ESTABLISHED 放行):取代已退役的
+ * 函数说明:1. N-32(P1 不变量 II,自愈收敛阶段1 补齐 ESTABLISHED 放行):取代已退役的
* `net_can_accept()`(只认 ESTABLISHED)作为监听 accept 门控——原门控使
* :7003 永不进 `net_accept()`,
- * `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的
+ * `net_lost_connect_probe`(CONNECT 事件未收到补 accept,N-30 实证的
* "数据被吞/425"主根因修复)对该端口等于从未运行
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
* `net_accept_locked` **内部**,门控挡在外面就永远到不了
- * 2b. 放行 ESTABLISHED(N34 阶段1 台架修正):单连接模型下 CONNECT 处理
+ * 2b. 放行 ESTABLISHED(自愈收敛阶段1 台架修正):单连接模型下 CONNECT 处理
* (net_poll 分派 / 探针补 accept / 对账② standalone accept)把监听 fd
* 转 ESTABLISHED,这正是"待 accept"电平——漏放行则任务周期慢的消费者
* (i104 200ms 抢不过对账② ~400ms 去抖)永不 accept、会话整场饿死
@@ -203,7 +203,7 @@ int net_listener_armed(int sockfd);
* 走调用方既有的错误分支重建,而不是静默卡死
* 4. ⚠ **不可作为唯一判据**:readable 只由 RECV_OK 中断置位,而
* net_reconcile_tick 的三类对账**不含**"芯片 RX 非空但 readable=0"
- * → 中断丢失时本函数会恒返 0。调用方须保留**低频兜底 recv**
+ * → 事件未收到时本函数会恒返 0。调用方须保留**低频兜底 recv**
* (ui_net_sink 每 32 轮无条件收一次,最坏延迟 ≈320 ms)
*/
int net_can_read(int sockfd);
@@ -233,7 +233,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen);
int net_send(int sockfd, const void *buf, int len, int flags);
/*
- * 函数功能:接收数据(TCP,**默认无限阻塞**——N34 阶段 1.5 语义主流化)
+ * 函数功能:接收数据(TCP,**默认无限阻塞**——自愈收敛阶段 1.5 语义主流化)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
@@ -254,7 +254,7 @@ int net_send(int sockfd, const void *buf, int len, int flags);
int net_recv(int sockfd, void *buf, int len, int flags);
/*
- * 函数功能:接收数据(非阻塞具名入口,原 NET_MSG_DONTWAIT 路径转正——N34 阶段 1.5)
+ * 函数功能:接收数据(非阻塞具名入口,原 NET_MSG_DONTWAIT 路径转正——自愈收敛阶段 1.5)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
@@ -268,7 +268,7 @@ int net_recv_nb(int sockfd, void *buf, int len);
/*
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK / Python
- * setblocking——N34 阶段 1.5,模式挂 socket 控制块属性)
+ * setblocking——自愈收敛阶段 1.5,模式挂 socket 控制块属性)
* 入口参数:sockfd - socket 描述符
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞
* 返回值:0 成功;-1 失败(描述符无效/未占用)
@@ -289,7 +289,7 @@ int net_setblocking(int sockfd, int on);
* timeout_ms - 单次调用在 netTask 内的最长等待(毫秒,>0)
* 返回值:>0 实际字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
* 限定条件:非 netTask 上下文;Socket 已连接
- * 函数说明:1. **显式预算**便捷入口(等价 Python settimeout,N34 阶段 1.5 起
+ * 函数说明:1. **显式预算**便捷入口(等价 Python settimeout,自愈收敛阶段 1.5 起
* 不再有隐藏默认值):net_recv 的无限阻塞 + 本接口的显式预算构成
* 超时语义的全部来源;超时首选仍是 net_select
* 2. E-02 修复配套:lftpd 等"等对端下一行/下一块数据"的调用方
@@ -326,7 +326,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
* 返回值:实际接收字节数,失败返回 -1
* 限定条件:socket 已创建,指针非空
* 函数说明:1. 读取 CH395F 接收缓冲区,获取对端地址
- * 2. 阻塞语义与 net_recv 同(N34 阶段 1.5):blocking 属性下无数据
+ * 2. 阻塞语义与 net_recv 同(自愈收敛阶段 1.5):blocking 属性下无数据
* 无限等待,DONTWAIT / 非阻塞属性立即 NET_ERR_WOULDBLOCK;
* 本接口为轮询自旋实现(历史遗留,UDP 专用),受 IWDG 监控的
* 任务同样禁止对其无限等待
@@ -344,14 +344,14 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
int net_close(int sockfd);
/*
- * 函数功能:主动拆掉当前 TCP 会话,**保留监听**(N34 阶段 3,E-02 收口)
+ * 函数功能:主动拆掉当前 TCP 会话,**保留监听**(自愈收敛阶段 3,E-02 收口)
* 入口参数:sockfd - socket 描述符
* 返回值:0 成功(含幂等:无活动会话),-1 失败
* 限定条件:非 netTask 上下文(经消息队列委托,同其它 net_* API)
* 函数说明:1. 适用场景=应用层放弃会话(对时 30s 预算耗尽、协议超时等):
* 单连接模型禁止对监听 fd net_close(连监听一起毁,AGENTS 约定),
* 而挂起但 TCP 仍活的客户端会永久占住单连接监听(PC 内核自动
- * ACK keepalive,芯片僵尸判不死——实验方案 E13 §5 定案缺口)
+ * ACK keepalive,芯片僵尸判不死——《网络自愈架构设计》§5.2 E13 定案缺口)
* 2. 实现=sock_reset QUARANTINE(FIN 关闭 + 排渣 + 排空事件 + 等
* byte0=00H,TRAP-NET-18 异常释放语义);auto_relisten fd 随即重监听
* (失败交 D10 重试),非 auto_relisten(client fd)收口 CLOSED
@@ -400,7 +400,7 @@ int net_get_errno(void);
*/
int net_sock_state(int sockfd);
-/* N34 阶段1(F-NET-3):net_resync_listen / net_relisten 声明已删——
+/* 自愈收敛阶段1(F-NET-3):net_resync_listen / net_relisten 声明已删——
* 静默回收形态由对账⑤自动认领(2 轮去抖 + 1s 冷却,纪律"不发芯片命令"
* 原样保留在认领动作内);监听重建由 reconciler 形态③④(100ms 周期)/
* D10(30ms 重试)单一所有者。勿再引入"应用层手动自愈"入口。 */
diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h
index 5d252e53..78adfa2a 100644
--- a/Drivers/BSP/NET/net_types.h
+++ b/Drivers/BSP/NET/net_types.h
@@ -49,7 +49,7 @@ extern "C" {
*/
#define NET_MSG_DONTWAIT 0x01 /* 非阻塞模式(单次覆盖 socket 的 blocking 属性;
* 具名入口见 net_recv_nb() / net_setblocking(),
- * N34 阶段 1.5) */
+ * 自愈收敛阶段 1.5) */
/*
* 网络字节序转换宏
@@ -112,7 +112,7 @@ struct net_sockaddr_in {
/*
* Socket 状态枚举(**本层会话投影**,非芯片状态事实源)
- * N34 阶段 3(镜像降级,实施方案 docs/网络/N34-自愈审计.md §5.5 项 5):
+ * 自愈收敛阶段 3(镜像降级,docs/网络/网络自愈架构设计.md §3.3):
* 芯片侧状态唯一事实源=驱动状态缓存(ch395f_sock_state_cached,刷新点=net_poll);
* 本 8 态只表达**本层会话语义**(fd 生命周期/待 accept 电平/门控展示),
* 事件与对账把它与芯片事实对齐。新增"按芯片状态决策"的代码一律读驱动缓存,
@@ -178,7 +178,7 @@ typedef struct {
/* 单连接模式自动重监听:连接断开后由 net 层自动回到 LISTEN(BSD 风格监听常驻) */
uint8_t auto_relisten;
- /* N34 阶段 1.5(2026-10-13):阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK /
+ /* 自愈收敛阶段 1.5(2026-10-13):阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK /
* Python setblocking 的"模式归使用者"模型)。
* 1 = 阻塞(net_socket() 默认);0 = 非阻塞(net_setblocking(fd, 0))。
* net_recv() 无 NET_MSG_DONTWAIT 时按本属性决定"无数据 → 登记挂起等待"
@@ -201,7 +201,7 @@ typedef struct {
* 均以此甄别幽灵态(见 net_socket.c TRAP-NET-20)。跨 fd 释放保留(幽灵属芯片
* Socket 级,与 fd 复用无关),新连接断开时刷新。 */
uint16_t last_peer_port;
- /* N-30:accept 路径"丢 CONNECT 中断补 accept"的去抖计数(连续确认次数,
+ /* N-30:accept 路径"CONNECT 事件未收到补 accept"的去抖计数(连续确认次数,
* 节拍 = accept 轮询 100ms;新监听/补 accept 后清零) */
uint8_t lost_conn_seen;
diff --git a/Drivers/BSP/README.md b/Drivers/BSP/README.md
index 18ec3f9a..657ccf5f 100644
--- a/Drivers/BSP/README.md
+++ b/Drivers/BSP/README.md
@@ -32,7 +32,7 @@
| 模块 | 陷阱 |
|---|---|
-| CH395F | SPI 事务须 `ch395f_spi_begin()/end()` 包裹;分频系数**必须为 8**(PCB 约束,勿"优化");快速复连会丢中断致监听死亡(TRAP-NET-17 已修);上传中止后首笔复连截断(TRAP-NET-18 已修) |
+| CH395F | SPI 事务须 `ch395f_spi_begin()/end()` 包裹;分频系数**必须为 8**(PCB 约束,勿"优化");快速复连会因事件被读清消费致监听死亡(TRAP-NET-17 已修,E14 复核:非芯片丢事件);上传中止后首笔复连截断(TRAP-NET-18 已修) |
| GD5F2GQ5UE | FatFs 移植版打开模式陷阱(`FA_OPEN_APPEND` 不含写位);文件名严格 8.3 短名、不得以点开头;`f_write` 簇分配失败返 `FR_OK` + 短写,须校验字节数 |
| NET | 就绪屏障位必须 `osFlagsNoClear`;`s_net_errno` 已按任务隔离(勿改回全局) |
| 通用 | 自研代码**禁用 C 库堆分配**(MicroLIB 堆仅 512 B),动态内存只用 `pvPortMalloc/vPortFree` |
diff --git a/Drivers/BSP/UI/ui_net_sink.c b/Drivers/BSP/UI/ui_net_sink.c
index 2be4a3f9..c807180b 100644
--- a/Drivers/BSP/UI/ui_net_sink.c
+++ b/Drivers/BSP/UI/ui_net_sink.c
@@ -479,7 +479,7 @@ int ui_net_sink_start(uint16_t port) {
* 3. 无客户端时**先门控**(net_listener_armed,纯 RAM 读)再 accept,成功则复位状态机。
* 门控判据 = "监听器还在服役"(LISTENING 或 CLOSED+auto_relisten)而非
* "本轮必然有连接"——后者会让断连待重建窗口内永不进 net_accept,
- * 把丢 CONNECT 中断的补 accept 探测永久挡在门外(N-32)
+ * 把 CONNECT 事件未收到的补 accept 探测永久挡在门外(N-32)
* 4. 有客户端时**先门控**(net_can_read)再非阻塞 recv 逐字节喂入状态机;
* 门控判为"无数据"时仍每 RM_LIVENESS_DIV 轮无条件收一次(兜底)
* 5. 对端关闭/真错误时关闭客户端。**不再自行重建监听**(N-32 不变量 II):
@@ -522,7 +522,7 @@ void ui_net_sink_poll(void) {
* ESTABLISHED〕换成 net_listener_armed〔LISTENING 或 CLOSED+auto_relisten〕)
* 原因:MCU 单连接模型下断连后 fd 会停在 CLOSED 等 relisten,旧门控在该窗口
* 内恒 0 → :7003 永不进 net_accept → net_lost_connect_probe 对该端口等于
- * 从未运行(丢 CONNECT 中断无第二信源);armed 放行后由 net_accept_locked
+ * 从未运行(CONNECT 事件未收到时无第二信源);armed 放行后由 net_accept_locked
* 的状态门自行裁决(含 RX 有数据的 closed-but-readable 接受) */
if (g_client < 0) {
if (net_listener_armed(g_listen) == 0) {
@@ -545,12 +545,12 @@ void ui_net_sink_poll(void) {
return;
}
- /* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底;N34 阶段 1.5:DONTWAIT 转正
+ /* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底;自愈收敛阶段 1.5:DONTWAIT 转正
* 为具名 API net_recv_nb——uiTask 在 IWDG required_mask 内且有 10ms UI 推流
* 周期职责,禁止无限阻塞 recv,非阻塞轮询是义务):
* 无数据可读 → 多数轮次直接返回,省掉一次 recv 往返(该往返内部
* osMessageQueuePut(osWaitForever) + xTaskNotifyWait(portMAX_DELAY),无上界);
- * 但每 RM_LIVENESS_DIV 轮无条件收一次,防 RECV_OK 中断丢失把数据永久藏住 */
+ * 但每 RM_LIVENESS_DIV 轮无条件收一次,防 RECV_OK 事件未收到把数据永久藏住 */
if (net_can_read(g_client) == 0) {
if (((++s_liveness_ctr) & (RM_LIVENESS_DIV - 1U)) != 0U) {
return;
@@ -574,7 +574,8 @@ void ui_net_sink_poll(void) {
rm_parser_reset();
/* N-32(P1 不变量 II):重建**不再**由本处自行 bind_listen。
* ① net_recv_locked 的 EOF 分支已触发 auto_relisten(D10 非阻塞重试);
- * ② 对账④还会兜底补一次 relisten(TRAP-NET-17 实证事件会丢,两层不可互替)。
+ * ② 对账④还会兜底补一次 relisten(TRAP-NET-17 实证事件面不是可靠唯一信源——
+ * E14 复核:非芯片丢事件——两层不可互替)。
* 本处再做一遍 = 第三个重建所有者,正是 §2.3 那 12 个所有者的同类。
* 仅当 fd 真消失(net_get_sock 返 NULL = 网络重建)才置 -1,交回上方
* 分支重新走 start → bind_listen */
diff --git a/Drivers/BSP/UI/ui_net_sink.h b/Drivers/BSP/UI/ui_net_sink.h
index bc2b1536..deabda79 100644
--- a/Drivers/BSP/UI/ui_net_sink.h
+++ b/Drivers/BSP/UI/ui_net_sink.h
@@ -47,7 +47,7 @@ int ui_net_sink_start(uint16_t port);
* 函数说明:1. 无客户端时先门控(net_listener_armed,纯 RAM 读)再尝试接受连接
* 2. 有客户端时先门控(net_can_read)再用 net_recv_nb 非阻塞收帧并解析
* (KEEPLIVE/INIT/KEY/LCDMEM);门控判为无数据时仍每 32 轮兜底收一次
- * (N34 阶段 1.5:DONTWAIT 转正为具名 API;uiTask 受 IWDG 监控,
+ * (自愈收敛阶段 1.5:DONTWAIT 转正为具名 API;uiTask 受 IWDG 监控,
* 非阻塞是契约义务——见 net_socket.h net_recv 文档契约)
* 3. 对端关闭或异常时复位客户端,等待下次重监听
* ⚠ **不是"零成本非阻塞"**:门控只消除无谓往返;真有活时仍是
diff --git a/docs/README.md b/docs/README.md
index 32a0f952..d9cb98da 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -95,7 +95,7 @@ docs/
| [`网络/BSD_Socket_API_使用指南.md`](网络/BSD_Socket_API_使用指南.md) | **上层视图**:BSD Socket API 公开语义、示例、常见问题 |
| [`网络/BSD_Socket_API_函数调用关系.md`](网络/BSD_Socket_API_函数调用关系.md) | **内部视图**:netTask / 消息队列 / L3 挂起 / 重试对账的调用关系与设计意图 |
| [`网络/Net层阻塞模型架构设计.md`](网络/Net层阻塞模型架构设计.md) | **等待模型**:pending 请求架构、五路完成语义、设计边界与性能特征 |
-| [`网络/N34-自愈审计.md`](网络/N34-自愈审计.md) | **N34 自愈审计(合并文档)**:net 层/驱动层/主流对照三路审计(F-NET/F-DRV/F-MS 编号发现)+ 汇总裁决与目标架构 + 分阶段实施方案 + E0~E13 实验记录;阶段 0~3 与 E6 已完成并转正 |
+| [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md) | **网络自愈架构**:现状权威描述——设计原则(不变量 I1~I8)、分层职责与三 tick、reconciler 形态①~⑤ + M3 门、驱动原语、recv 语义、KeepAlive、参数表与 E 锚点速查、红线与延后项;审计过程(T1~T4/E0~E13)已归档 |
---
@@ -272,7 +272,9 @@ docs/
| 日期 | 变更 | 说明 |
|---|---|---|
-| 2026-10-13 | N34 审计归档 | 原 `docs/网络/N34-自愈审计/` 目录 7 份文档(根因总览 / net 层盘点 / 驱动层审计 / 主流对照 / 汇总裁决 / 实施方案 / 实验方案)**合并为单文档 [`网络/N34-自愈审计.md`](网络/N34-自愈审计.md)**,原目录删除;全库引用同步(`docs/` 4 篇 + 驱动/网络代码注释 + 配图生成器与 SVG)。本索引 §5.1 补登记 |
+| 2026-10-14 | E14"丢中断"复核与归因更正 | 48 轮直读 SINT 矩阵复核历史"丢中断"说法:**芯片侧事件丢失 = 0**("丢失"= drain 读清消费 3/48 + 门控竞态 2/48 + 主机同步 1/48,幽灵态 0/48 未复现);根除性修复 = listen/relisten 路径排事件挪至 LISTEN 命令**之前**(吞新会话 CONNECT 的生产侧自伤窗);全局读收口(全局寄存器唯一读者=`net_poll`,I7 护栏);全库 17 文件约 40 处"丢中断"归因更正为"事件未收到/读清消费竞态",`docs/网络/网络自愈架构设计.md` §5.2 新增 E14 锚点;台架保留复核探针(drain-int/SINT 补读×3/超时归类) |
+| 2026-10-13 | 网络自愈架构文档瘦身与改名 | 合并文档 `网络/N34-自愈审计.md`(2062 行审计全记录)瘦身为**终版设计描述**(281 行:不变量 I1~I8 / 架构与机制详解 / 参数与 E 锚点速查 / 红线 / 延后项),并经 `rename_ref.py` 改名 [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md)(全库 16 文件 30 处引用同步);审计过程(T1~T4、E0~E13)归档 git 历史,考古命令见该文档「引用与归档约定」 |
+| 2026-10-13 | 网络自愈审计归档 | 原 `docs/网络/N34-自愈审计/` 目录 7 份文档(根因总览 / net 层盘点 / 驱动层审计 / 主流对照 / 汇总裁决 / 实施方案 / 实验方案)**合并为单文档 [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md)**,原目录删除;全库引用同步(`docs/` 4 篇 + 驱动/网络代码注释 + 配图生成器与 SVG)。本索引 §5.1 补登记 |
| 2026-10-12 | 新增环境与部署 | 新建 `docs/环境与部署/并行任务工作树方案.md`(**多任务并行开发机制规格**:`git worktree` 隔离、目录与共享/独占分配、`task-worktree` / `@task` skill 设计、单写者文档与合并残留审计、台架锁;**已落地并端到端实测**);本索引 §9.2 补登记 |
| 2026-10-12 | 远程 CI 文档独立成目录 | 新建 `docs/CI/`:原散在 4 处的远程 CI 内容(`环境与部署/CI配置指南.md` 477 行 + `环境与部署/Gitea_Runner部署指南.md` 917 行 + `测试开发标准流程.md` §9 + `AGENTS.md` 提交门禁节 4 条,共约 2000 行)**合并为 `CI/远程CI完整指南.md` 一篇**,`CI/README.md` 作唯一入口;`AGENTS.md` 压成一行指针、测试流程 §9 只留覆盖边界与本地等价。动因:远程 CI **只跑在本机一台 runner**,与日常开发规范混在一起每次会话都被读,造成困惑 |
| 2026-10-11 | 功能 | N-19 FTP 鉴权:`/config.ini` 新增 `[ftp]` 段(`user` / `pwd_hash` = SHA-256(user‖password) / `write`)+ 恒定时间比较 + 连续 5 次失败锁 60 s + 未登录命令门;出厂默认 admin/admin,默认口令在用有启动告警。新增 `App/util/sha256.*`、`Drivers/BSP/NETCFG/ftp_cfg.*`、`Drivers/BSP/NET/lftpd/lftpd_auth.*`;主机侧单测 `test/host/sha256/`(13 例)+ `test/host/ftp_auth/`(36 例),pre-commit 钩子 26→28;台架脚本 `test/host/ftp_auth_bench.py`。文档同步:`docs/子系统设计/网络配置说明.md`、`docs/架构设计/STM32F4-Base存储架构说明.md`、`docs/代码规范/pre-commit使用说明.md`、`docs/产品需求/产品需求设计文档.md` |
diff --git a/docs/UI设计/RemoDispBus协议与接收设计说明.md b/docs/UI设计/RemoDispBus协议与接收设计说明.md
index cfab465a..ce0f01fe 100644
--- a/docs/UI设计/RemoDispBus协议与接收设计说明.md
+++ b/docs/UI设计/RemoDispBus协议与接收设计说明.md
@@ -244,7 +244,7 @@ if (n > 0) {
> ✅ **门控已落地(2026-10-10)**:`ui_net_sink_poll()` 现在**先查后动**——`net_can_accept()` /
> `net_can_read()` 纯 RAM 读控制块判"有没有活",无活直接返回,不再每 10 ms 发一次
> `net_accept`(原 100 msg/s 队列压力,与 FTP 抢深度 8 的队列)。门控判"无数据"时仍
-> **每 32 轮无条件 recv 一次**兜底(`readable` 只由 RECV_OK 中断置位,中断丢失时纯门控
+> **每 32 轮无条件 recv 一次**兜底(`readable` 只由 RECV_OK 中断置位,事件未收到时纯门控
> 会把数据永久藏住)。模拟器后端恒返回"就绪",行为逐位不变。
>
> ⚠ **残留暴露(已知,刻意不处理)**:门控只消除"无谓的往返",消除不了"往返本身无上界"——
diff --git a/docs/images/网络不稳根因总览.svg b/docs/images/网络不稳根因总览.svg
index b2d36251..bfd714fa 100644
--- a/docs/images/网络不稳根因总览.svg
+++ b/docs/images/网络不稳根因总览.svg
@@ -1,20 +1,20 @@
diff --git a/docs/任务规划/基线与待确认.md b/docs/任务规划/基线与待确认.md
index 7c77f53a..e92eca5a 100644
--- a/docs/任务规划/基线与待确认.md
+++ b/docs/任务规划/基线与待确认.md
@@ -13,7 +13,7 @@
### 2.1 pending 二期设计基线(ACCEPT + SEND)
-> **来源**:一期(RECV)架构现状见 `docs/网络/Net层阻塞模型架构设计.md`(含演进溯源附录);
+> **来源**:一期(RECV)架构现状见 `docs/网络/Net层阻塞模型架构设计.md`(演进史查 git 历史);
> 本节仅保留**二期仍要用的设计基线**。
**一期已验证的机制骨架**(二期沿用,勿重新发明):独立静态 pending 表 + `net_pending_tick`
diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md
index ca4f3730..7e27a4fa 100644
--- a/docs/内存管理/RAM使用分析与优化方案.md
+++ b/docs/内存管理/RAM使用分析与优化方案.md
@@ -36,10 +36,10 @@
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
-| ER_IROM1 | 230,216 B (224.82 KB) | 1,048,576 B (1024 KB) | 22.0% |
+| ER_IROM1 | 230,208 B (224.81 KB) | 1,048,576 B (1024 KB) | 22.0% |
| RW_IRAM1 | 97,912 B (95.62 KB) | 131,072 B (128 KB) | 74.7% |
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
-| **Flash (ROM 合计)** | **231,320 B (225.9 KB)** | 1,048,576 B (1024 KB) | **22.1%** |
+| **Flash (ROM 合计)** | **231,312 B (225.9 KB)** | 1,048,576 B (1024 KB) | **22.1%** |
各区的容量与常量来源:
@@ -110,7 +110,7 @@
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-10-10 实测峰值 / 静态最坏链 |
|---|---|---|---|---|
| uiTask | `4096*4` | 16KB | LVGL 1bpp 离屏渲染(渲染缓冲在静态区,栈只走调用链) | 4016 B(余 75%)/ 静态 1336 B → 可大幅缩减 |
-| netTask | `960*4` | 3.75KB | CH395F 轮询(DMA 搬运,栈压力小) | 920 B(余 76%)/ 静态 **1944 B**(2026-10-12 N32 监听模块化后复测;N34 阶段 3 合并后复测 **1944 B** 不变,链尾经 net_listener_retry_tick→net_relisten_locked) |
+| netTask | `960*4` | 3.75KB | CH395F 轮询(DMA 搬运,栈压力小) | 920 B(余 76%)/ 静态 **1944 B**(2026-10-12 N32 监听模块化后复测;自愈收敛阶段 3 合并后复测 **1944 B** 不变,链尾经 net_listener_retry_tick→net_relisten_locked) |
| ftpTask | `1024*4` | 4KB | lftpd FTP 服务 | 740 B(余 81%)/ 静态 1216 B |
| testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B |
| defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(N-33 抬栈 1024→1280,2026-10-12) |
diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md
index 4c6e618a..234c1196 100644
--- a/docs/内存管理/RAM预算台账.md
+++ b/docs/内存管理/RAM预算台账.md
@@ -15,7 +15,7 @@
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---|
-| ER_IROM1 (Flash) | 230,216 B | 1,048,576 B | 22.0% | 参考 |
+| ER_IROM1 (Flash) | 230,208 B | 1,048,576 B | 22.0% | 参考 |
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
| RW_IRAM1 | 97,912 B | 131,072 B | 74.7% | PASS |
diff --git a/docs/子系统设计/IEC104上行设计说明.md b/docs/子系统设计/IEC104上行设计说明.md
index 927f892e..86ba9891 100644
--- a/docs/子系统设计/IEC104上行设计说明.md
+++ b/docs/子系统设计/IEC104上行设计说明.md
@@ -131,9 +131,9 @@
数据通道复用同一 socket(accept 返回即 listen fd);EOF 时 net 层 auto_relisten,
应用层**不得**关 fd(`client == listen` 分支);仅模拟器多连接后端(fd 不同)显式关。
- **门控 + 兜底**:`net_listener_armed`/`net_can_read`(纯 RAM 读)省无谓 RPC 往返
- (N34 阶段1:门控从只认 ESTABLISHED 的 `net_can_accept` 迁移——旧门控把监听
- 服役窗口全挡掉,丢 CONNECT 探针从不运行);
- 每 32 轮无条件 recv 一次防 RECV_OK 中断丢失(ui_net_sink I-1/I-2 先例)。
+ (自愈收敛阶段1:门控从只认 ESTABLISHED 的 `net_can_accept` 迁移——旧门控把监听
+ 服役窗口全挡掉,CONNECT 事件补 accept 探针从不运行);
+ 每 32 轮无条件 recv 一次防 RECV_OK 事件未收到(ui_net_sink I-1/I-2 先例)。
- **网络代次自愈**:`net_get_generation()` 变化 → 关旧 fd 重建监听(netTask
NET_RECONFIG 重建路径的既有约定)。
- **主动拆链**(与 EOF 路径相对):协议故障(坏帧/N(R) 倒退/t1 超时)或发送连续
diff --git a/docs/子系统设计/网络Socket监听生命周期设计说明.md b/docs/子系统设计/网络Socket监听生命周期设计说明.md
index 5cdb69f0..f4c7b3b7 100644
--- a/docs/子系统设计/网络Socket监听生命周期设计说明.md
+++ b/docs/子系统设计/网络Socket监听生命周期设计说明.md
@@ -65,7 +65,7 @@ B10 探针看到的"孤儿芯片态"不是芯片异常,而是本仓这个配
另有一条**探针失效**:`net_can_accept`(`:1453`)要求 ESTABLISHED 才返 1,
而 `Drivers/BSP/UI/ui_net_sink.c:512` 用它门控 accept → :7003 **永不进 `net_accept`**
→ TRAP-NET-20 的 `net_lost_connect_probe`(`:1250-1289`)对这个端口**从不运行**,
-丢 CONNECT 只剩对账②。:21/:8888 因为无条件轮询 accept,探针才活着。
+CONNECT 事件未收到时只剩对账②。:21/:8888 因为无条件轮询 accept,探针才活着。
### 2.4 FUN_PARA bit3=1 的必要性与安全边界(2026-10-12 评估,结论:**必须保留 0x08**)
@@ -112,16 +112,16 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
| 契约要求 | 对应缺口 | 收口手段 |
|---|---|---|
| 「会话结束」必须被**谁都能看见**,不能只在边沿那一刻 | EOF 边沿化:抢先 relisten → recv 永久 `WOULDBLOCK`(§2.3) | 不变量 I:`net_sock_t.session_dead` 粘滞电平,`net_recv_locked` DONTWAIT 判据改 `state==CLOSED \|\| session_dead`(P1) |
-| 关闭 Socket 必须**一定有人做**、且只做一次 | 重建所有者 12 个 + U12 的 LISTEN 失败不回 close | 不变量 II:`net_listener_pump` 单点重建 + 补 U12 的 close 对称(P2)(N34 阶段 3:pump 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick——不变量 II 不变,见 §10.10) |
+| 关闭 Socket 必须**一定有人做**、且只做一次 | 重建所有者 12 个 + U12 的 LISTEN 失败不回 close | 不变量 II:`net_listener_pump` 单点重建 + 补 U12 的 close 对称(P2)(自愈收敛阶段 3:pump 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick——不变量 II 不变,见 §10.10) |
| 关闭姿势必须正确 | TRAP-NET-12:陈旧 DISCONNECT 锁存秒断下一条连接 | `net_close_locked` 既有「close → 等 `CLOSED` → 排空本 Socket 中断」,是 bit3=1 下唯一正确的关闭序列 |
| 异常释放须先排空残渣再关 | TRAP-NET-18:~2KB RX 残渣被下条连接当自己的数据读走 | `net_relisten_locked` 隔离期(400 ms)持续排空 RX + 主动 close + 等 `CLOSED/TCP_CLOSED` |
-> **命名注(N34 阶段 1.5,2026-10-13)**:上表与下文所说的「DONTWAIT」机制本体不变
+> **命名注(自愈收敛阶段 1.5,2026-10-13)**:上表与下文所说的「DONTWAIT」机制本体不变
> (`NET_MSG_DONTWAIT` 仍是 `net_recv_locked` 的非阻塞路径与 EOF 判据载体),但对外
> 入口已主流化:具名 API `net_recv_nb()`(单次非阻塞)+ `net_setblocking(fd, on)`
> (模式挂控制块 `blocking` 属性);`net_recv()` 默认改为**无限阻塞**(30s 默认预算
> 删除),受 IWDG 监控任务的有界等待契约见 `docs/网络/BSD_Socket_API_使用指南.md`
-> §10.1 与 `docs/网络/N34-自愈审计.md` §5.3.5。本文所有「DONTWAIT 判据 /
+> §10.1 与 `docs/网络/网络自愈架构设计.md` §4.5。本文所有「DONTWAIT 判据 /
> 返 WOULDBLOCK」表述按新命名逐字仍成立,不逐处改写。
**⑥ 本次(P0)已落地的口径修正**:`net_socket.c:3627` / `:3641` 原注释写「DISCONNECT 后
@@ -149,10 +149,10 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
- **不变量 I(电平化)**:「这条会话已结束」必须是**可重复查询、幂等**的状态,
而不是"恰好在那一刻观察到才算"。谁先看到不重要,**谁都饿不着**。
- **不变量 II(单一所有者)**:监听器的重建**只有一个决定者**(net 层的 listener pump)。
- 其它所有位置只**置事实**,不**做动作**。(N34 阶段 3 起该决定者 = `net_reconcile_tick` 形态③④,pump 已并入,见 §10.10)
+ 其它所有位置只**置事实**,不**做动作**。(自愈收敛阶段 3 起该决定者 = `net_reconcile_tick` 形态③④,pump 已并入,见 §10.10)
两条不变量成立后,"谁重建""会不会错过"这两类问题在结构上消失,
-对账④从"唯一救命稻草"降级为"事件丢失时的补漏"(TRAP-NET-17 实证事件会丢,故对账**必须保留**)。
+对账④从"唯一救命稻草"降级为"事件面未收到时的补漏"(TRAP-NET-17 实证事件面不是可靠唯一信源——E14 复核:事件非芯片侧丢失、被读清消费——故对账**必须保留**)。
## 5. 分阶段方案
@@ -185,7 +185,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
### P2 单一所有者(中改,消灭多所有者)—— P2a ✅ 2026-10-12 / P2b 建议暂缓
1. 新增 `net_listener_pump()`(100 ms 周期),接管**周期性**重建,是唯一做周期性
- `net_relisten_try` 的地方。✅ 已落地(N34 阶段 3 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick,见 §10.10)
+ `net_relisten_try` 的地方。✅ 已落地(自愈收敛阶段 3 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick,见 §10.10)
2. ⚠ **原第 2 条已被实测推翻**:原计划「删除 `net_accept_locked` / `net_recv_locked` /
`net_pending_tick` 三处即时重建」。实测删掉会把 CLOSED 窗口从 ~0 拉长到 ~100 ms,
调用方高频空转 `net_accept`(RPC 无上界)→ uiTask 最坏单轮 172 ms → 1034 ms + `[WD] task stall`。
@@ -209,7 +209,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
P0 → P1 → P2 严格串行,每阶段独立提交、独立回归、可单独回滚。
P1 是性价比最高的一步:它不改变 fd 模型,却把 B10 与"消费者卡死"两类事故同时从结构上消掉。
-P2 之后,对账④保留但只作事件丢失的补漏(TRAP-NET-17 实证事件会丢,不可删)。
+P2 之后,对账④保留但只作事件面未收到的补漏(TRAP-NET-17 实证事件面不是可靠唯一信源,不可删)。
## 7. 证据附录(2026-10-12 实测与调研,供后续执行时复核)
@@ -227,8 +227,8 @@ P2 之后,对账④保留但只作事件丢失的补漏(TRAP-NET-17 实证
- **EXAM5(多连接)**:`main.c:296 SetFunPapr(0x02)`(bit1=1);Socket0 `OPEN+TCPListen` 后再不碰(:117-127),Socket1~7 只配端口/协议**不 OPEN**(:129-133);DISCONNECT/TIM_OUT **只打印、不 close 不重 LISTEN**(:203-212)——恢复完全由芯片自动,**只在多连接模式成立**。
- **EXAM10(单连接,FTP Server)**:DISCONNECT → `CH395.C:234` 显式 `CloseSocket` → `:237-238` **立即重新 OPEN + 重新 TCP_LISTEN**;TIM_OUT 同路径(`:250`/`:254`);调用者是中断处理器;close→reopen **无延时无重试**,失败 `mStopIfError` 挂死。
-- **结论**:单连接模式下官方**零自动重监听**,且原生做法是**在事件现场当场重建**;本仓对账④是周期兜底,方向一致但不可互替(TRAP-NET-17 实证事件会丢)。
-- **中断所有权**:全局寄存器整体读即清、无按位清除(`docs/芯片手册/CH395F.md` §5.15 L441);CONNECT/DISCONNECT/TIM_OUT 是**事件位**(可丢)、RECV 是**条件位**(可补)→ 任何"只靠事件重建监听"的方案天然不完备。
+- **结论**:单连接模式下官方**零自动重监听**,且原生做法是**在事件现场当场重建**;本仓对账④是周期兜底,方向一致但不可互替(TRAP-NET-17 实证事件面不是可靠唯一信源)。
+- **中断所有权**:全局寄存器整体读即清、无按位清除(`docs/芯片手册/CH395F.md` §5.15 L441);CONNECT/DISCONNECT/TIM_OUT 是**事件位**(锁存但读即清,可被其它读者消费掉),RECV 是**条件位**(电平语义、可补)→ 任何"只靠事件重建监听"的方案天然不完备。
### 7.3 状态机事实(`Drivers/BSP/NET/net_socket.c` 3921 行)
@@ -259,8 +259,8 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56
|---|---|---|
| D1 | **A′(启用芯片多连接模式)出局**,根治必须在单连接复用模型内做 | `docs/问题记录/CH395F_Trap_Records.md` 附 FTP 改造清单第 4 条「多连接模式不支持多监听」;本产品 3 常驻监听 + 动态 PASV;§9.2.6 L1295 池按源端口匹配会互串;历史实测 TRAP-NET-02/-11;FUN_PARA 初始化后不可改(`docs/芯片手册/CH395F.md` §5.52 L795⑥) |
| D2 | 采用**形态 B 深化**(电平化 + 单一所有者),**不做 P3** | P3 需控制面重写、RAM/Flash 上升,只有"同端口并发多连接"需求才值得 |
-| D3 | 对账④**保留**(`4f2cc8b` 不撤回),其副作用由 P1 消除 | TRAP-NET-17 实证事件会丢 → 周期对账不可删;④ 已把 `:7003` 从 0/10 永久死拉到 10/10 |
-| D4 | `FUN_PARA` **保持 `0x08`**,不因孤儿态改配置(2026-10-12 复核确认) | bit3=1 是 TRAP-NET-15/-20 定案的"保留排空现场"契约;bit3=0 会令芯片在 DISCONNECT/TIM_OUT 时**当场清缓冲**(`docs/芯片手册/CH395F.md` §5.52② / §5.23 L572)→「发完即 FIN」流量的尾部数据必被硬件擦除、**不可补救**;且 bit3=0 也挡不住丢中断(TRAP-NET-17)→ 孤儿态照样有。孤儿态是 bit3=1 的合法后果,应在软件层收口而非回退契约。完整论证见 §2.4 |
+| D3 | 对账④**保留**(`4f2cc8b` 不撤回),其副作用由 P1 消除 | TRAP-NET-17 实证事件面不是可靠唯一信源(E14 复核:非芯片侧丢失,是读清消费)→ 周期对账不可删;④ 已把 `:7003` 从 0/10 永久死拉到 10/10 |
+| D4 | `FUN_PARA` **保持 `0x08`**,不因孤儿态改配置(2026-10-12 复核确认) | bit3=1 是 TRAP-NET-15/-20 定案的"保留排空现场"契约;bit3=0 会令芯片在 DISCONNECT/TIM_OUT 时**当场清缓冲**(`docs/芯片手册/CH395F.md` §5.52② / §5.23 L572)→「发完即 FIN」流量的尾部数据必被硬件擦除、**不可补救**;且 bit3=0 也挡不住事件面未收到(TRAP-NET-17)→ 孤儿态照样有。孤儿态是 bit3=1 的合法后果,应在软件层收口而非回退契约。完整论证见 §2.4 |
| D5 | 顺序 **P0 → P1 → P2**,每阶段独立提交、独立回归、可单独回滚 | P1 不动 fd 模型却消掉两类事故,性价比最高 |
| D6 | 真机/模拟器**双模型分裂暂不消除** | 消除即等于做 P3;先靠 P1 把应用侧契约对齐 |
| D7 | 同族残留**不在本次根治范围**,各自单列:U1(CONNECTING 滞留,B2 残余)、U11、U12、U14、U15,以及审计项 B4/B5/B6/B7/B8/B9 | 与监听生命周期不同因,混做会让回归面失控 |
@@ -269,7 +269,7 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56
**P0(零风险)**
- [x] 修 `Drivers/BSP/NET/net_socket.c:3627`、`:3641` 的 FUN_PARA 口径错误注释(实际 `0x08`,芯片**不**自动关)—— 2026-10-12 已改并留痕(§2.4⑥),`@build` 0 错 0 警
-- [x] 把 U11 / U12 / U14 / U15 登记进 `docs/问题记录/CH395F_Trap_Records.md`——**实际落点改 N34 审计文档**:U11/U12/U14 机制归属见 `docs/网络/N34-自愈审计.md` §1;U15 经 E6 裁决(命中=0)登记观察项(`docs/网络/N34-自愈审计.md` §6 E6 结果)
+- [x] 把 U11 / U12 / U14 / U15 登记进 `docs/问题记录/CH395F_Trap_Records.md`——**实际落点改 《网络自愈架构设计》**:U11/U12/U14 机制归属见 `docs/网络/网络自愈架构设计.md` §4.1;U15 经 E6 裁决(命中=0)登记观察项(`docs/网络/网络自愈架构设计.md` §4.8)
- [x] 在 B10 行确认已记「④ 扩大 EOF 吞没窗口」——2026-10-13 复核:新 TRAP-NET 文档 B10 行在位(✅ 已修 + 副作用注记 + `net_drop_conn` 根治出口)
**P1(边沿→电平)**
@@ -333,7 +333,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
**P2a(✅ 2026-10-12 落地,提交 `7cb5af0`)**
- [x] 新增 `net_listener_pump()`(100 ms 周期 + 去抖 + 冷却),接管**周期性**重建,
覆盖:① LISTENING 失位 ② CLOSED 不在 LISTEN ③ CLOSED 但芯片**正在** LISTEN(U11)
- (N34 阶段 3 已并入 `net_reconcile_tick` 形态③④/U11,单一 100ms tick,见 §10.10)
+ (自愈收敛阶段 3 已并入 `net_reconcile_tick` 形态③④/U11,单一 100ms tick,见 §10.10)
- [x] 删对账③④ 及其孤儿态认领门槛(对账只剩①②;认领收窄为 ESTABLISHED/LISTENING)
- [x] 补 U11:本层 CLOSED 而芯片正在 LISTEN → **只做状态重同步**(含 `last_peer_port`
转登 + `lost_conn_seen` 清零,同 `net_resync_listen`),不发任何芯片命令
@@ -347,7 +347,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
同样存在(P1 基线同条件 53 条),忙等的危害是**理论性**的;而 `net_close` 是每次会话
结束都走的热路径。按 TRAP-NET-20「不留未证明的复杂度」,为一个未发生的故障去动热路径,
性价比存疑 —— 建议等有 `task stall` 与忙等的**因果证据**后再做。
- (**N34 阶段 3 已实施 2026-10-13**:不是"为忙等而改",而是驱动 `sock_reset` 原语落地后
+ (**自愈收敛阶段 3 已实施 2026-10-13**:不是"为忙等而改",而是驱动 `sock_reset` 原语落地后
顺路收口——`net_sock_reset_sync` 确定性排渣替代盲等(GRACEFUL 100ms 底 / 600ms 封顶),
并删除 `net_close_locked` / `net_connect_locked` 的全局中断读清(F-NET-7),见 §10.10)
@@ -374,7 +374,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
> **动机**:真机 net 层 `net_socket.c` 4126 行,而模拟器做同样的事只有 807 行(5 倍);
> `relisten` 一词在文件里出现 110 次。用户诉求 = **高内聚低耦合**。
> P2a 把「周期性重建」收敛到 `net_listener_pump`(所有者 7→5)之后,搬移边界才**第一次清晰**——
-> 这正是 P2a 相对"直接抽模块"的价值:先收敛,再搬移。(N34 阶段 3 起 pump 已并入 `net_reconcile_tick` 形态③④,见 §10.10)
+> 这正是 P2a 相对"直接抽模块"的价值:先收敛,再搬移。(自愈收敛阶段 3 起 pump 已并入 `net_reconcile_tick` 形态③④,见 §10.10)
>
> 本节只定**边界与接口**,不含任何代码;执行须过 §10.6 的验证门。
@@ -401,7 +401,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| 常量 | **8** | `NET_RELISTEN_{QUARANTINE,GRACEFUL,SETTLE}_MS` / `NET_RELISTEN_SETTLE_{QUARANTINE,GRACEFUL}` / `NET_RELISTEN_{RETRY,DEADLINE}_MS` / `NET_LISTENER_PUMP_PERIOD_MS` |
| **搬移量小计** | **≈470 行** | 函数块 414 + 常量/状态注释块 55 |
-> ⚠ **N34 阶段 3(2026-10-13)追注**:上表是 2026-10-12 搬移时的历史清点,原文保留。此后 `net_listener_pump` 已**删除**(重建决策并入 `net_socket.c` 的 `net_reconcile_tick` 形态③④/U11,单一 100ms tick),`NET_LISTENER_PUMP_PERIOD_MS` 等 pump 常量同删、新增 `NET_RECONCILE_PERIOD_MS` / `NET_RECON_STABLE_*`——见 §10.10。
+> ⚠ **自愈收敛阶段 3(2026-10-13)追注**:上表是 2026-10-12 搬移时的历史清点,原文保留。此后 `net_listener_pump` 已**删除**(重建决策并入 `net_socket.c` 的 `net_reconcile_tick` 形态③④/U11,单一 100ms tick),`NET_LISTENER_PUMP_PERIOD_MS` 等 pump 常量同删、新增 `NET_RECONCILE_PERIOD_MS` / `NET_RECON_STABLE_*`——见 §10.10。
**跨边界引用点**(这个数字决定接口面大小,是本设计的核心依据):
@@ -422,7 +422,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| 搬入 | 理由 |
|---|---|
| `net_relisten_locked` / `net_relisten_try` / `net_relisten_retry_tick` / `net_relisten_clear` / `net_relisten_done` | 「维持 fd 处于 LISTEN 态」的全部机制 |
-| `net_listener_pump` | 唯一的周期性重建决策者(P2a 成果)(N34 阶段 3 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick,见 §10.10) |
+| `net_listener_pump` | 唯一的周期性重建决策者(P2a 成果)(自愈收敛阶段 3 已并入 `net_reconcile_tick` 形态③④,单一 100ms tick,见 §10.10) |
| `net_drain_chip_rx` | 重建前的排空(只被 relisten 调用) |
| 7 个 `s_relisten_*` + 8 个 `NET_RELISTEN_*` / `PUMP_PERIOD` 常量 | 状态与策略私有化 |
@@ -434,8 +434,8 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
| `net_accept_locked` | 产出的是**连接**(LISTENING→ESTABLISHED 迁移),属连接层;它只**调用**新模块 |
| `net_lost_connect_probe` | 做的是"补 accept"(连接建立),不是重建监听 |
| `net_listener_armed` | **双端公开 API**(sim 也实现);纯 RAM 查询,很薄,留在原处避免 sim 缺符号 |
-| ~~`net_resync_listen`~~ | **N34 阶段1 已删除**:其"只改本层状态"语义并入对账⑤(静默回收形态自动认领,纪律"不发芯片命令"随动作沉淀进 `net_reconcile_tick`),手动 API 面退役 |
-| `net_reconcile_tick` | ①②⑤ 是**连接**失同步自愈(③④ 已在 P2a 移交 pump;⑤ 为 N34 阶段1 新增,吸收原 `net_resync_listen` 语义)(N34 阶段 3:③④/U11 已**回到本 tick**,单一 100ms tick 覆盖①②③④⑤/U11,见 §10.10) |
+| ~~`net_resync_listen`~~ | **自愈收敛阶段1 已删除**:其"只改本层状态"语义并入对账⑤(静默回收形态自动认领,纪律"不发芯片命令"随动作沉淀进 `net_reconcile_tick`),手动 API 面退役 |
+| `net_reconcile_tick` | ①②⑤ 是**连接**失同步自愈(③④ 已在 P2a 移交 pump;⑤ 为 自愈收敛阶段1 新增,吸收原 `net_resync_listen` 语义)(自愈收敛阶段 3:③④/U11 已**回到本 tick**,单一 100ms tick 覆盖①②③④⑤/U11,见 §10.10) |
| `net_sock_t` 全部字段 | 数据归 `net_types.h`(双端共享核心),**不随模块走** |
> ⚠ `net_sock_t` 的 `last_peer_port` / `lost_conn_seen` 是**跨层字段**:新模块的
@@ -560,9 +560,9 @@ void net_listener_tick(void);
- 改完文件**必须重新 `git add`** —— pre-commit 校验的是**暂存区快照**,漏 add 会让
「本地已修好」与「提交时仍报错」自相矛盾(本次踩到,白跑两轮)。
-### 10.10 N34 阶段 3:pump 并入 reconciler(**已执行 2026-10-13**)
+### 10.10 自愈收敛阶段 3:pump 并入 reconciler(**已执行 2026-10-13**)
-审计(`docs/网络/N34-自愈审计.md` §5.5 项 1)指出:`net_reconcile_tick`(200ms,①②⑤)与
+自愈架构审计(`docs/网络/网络自愈架构设计.md` §4.1 表注留痕沿革)指出:`net_reconcile_tick`(200ms,①②⑤)与
`net_listener_pump`(100ms,③④/U11)是**同一件事的两个半片**——都在"读芯片状态 → 比对镜像 → 分级处置",
拆两个 tick 只是历史演化(P2a 从对账里拆出去),既多一份去抖/冷却状态机,又让"对账① 置 CLOSED 后
要等下一个 pump 轮才重建"多一跳延迟。阶段 3 合并为**单一 100ms `net_reconcile_tick`**:
diff --git a/docs/测试说明/CH395F_Test_Guide.md b/docs/测试说明/CH395F_Test_Guide.md
index f96290d6..808680f2 100644
--- a/docs/测试说明/CH395F_Test_Guide.md
+++ b/docs/测试说明/CH395F_Test_Guide.md
@@ -1094,17 +1094,17 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON
| **类型** | 功能测试 |
| **标题** | TCP 连接空闲超过 KeepAlive 阈值后自动断开 |
| **前置条件** | 1. `NET_KEEPALIVE_IDLE_MS=60000` / `INTVL=5000` / `CNT=3`(`net_config.h`)
2. 对端须为**死对端**:PC 连接 8080 后**禁用网卡**制造"链路在、对端静默"(存活对端会 ACK 探测 → 永不 TIMEOUT;Windows 防火墙规则对已建立连接无效,E11 实证)
3. 勿与 Phase 6/9/10/13 混选(recv 互抢数据) |
-| **测试步骤** | 1. PC 连接 8080,不发数据
2. 立即禁用 PC 网卡(中间有交换机 → 设备侧 PHY 链路保持)
3. 等待芯片报 TIM_OUT(实测判死窗:连接起 **18~31s**,芯片定时语义与手册公式 75s 不符,见实验方案 E11/E13)
4. 本层 QUARANTINE → reconciler 形态③④ 重建 → s0 回 LISTEN
5. 恢复网卡,重连 8080 验证端口已恢复 |
+| **测试步骤** | 1. PC 连接 8080,不发数据
2. 立即禁用 PC 网卡(中间有交换机 → 设备侧 PHY 链路保持)
3. 等待芯片报 TIM_OUT(实测判死窗:连接起 **18~31s**,芯片定时语义与手册公式 75s 不符,见 `docs/网络/网络自愈架构设计.md` §5.2 E11/E13)
4. 本层 QUARANTINE → reconciler 形态③④ 重建 → s0 回 LISTEN
5. 恢复网卡,重连 8080 验证端口已恢复 |
| **通过标准** | recv 返回 0 且耗时落在 **10s~120s 有界窗**(下限防"存活空闲会话被误杀"——T2 实测存活对端静默 90s 不触发;上限防"判死缺位");s0 回 LISTEN 且可接受新连接。报告后测试自动释放 socket 等新连接再测(一次性,防对已死连接 10ms 节奏刷假 FAIL) |
---
-## 12.5 阶段 15:N34 阶段 2 驱动原语(硬件层,无需 PC)
+## 12.5 阶段 15:自愈收敛阶段 2 驱动原语(硬件层,无需 PC)
-验证 `ch395f` 新增自愈原语(`docs/网络/N34-自愈审计.md` §5.4;形态定案
-`docs/网络/N34-自愈审计.md` §6 E2/E3/E4)。硬件层模式独占芯片,用 Socket 7
+验证 `ch395f` 新增自愈原语(`docs/网络/网络自愈架构设计.md` §4.4;形态定案锚点
+同文档 §5.2 E2/E3/E4)。硬件层模式独占芯片,用 Socket 7
(产品监听 0~4 不涉),无需 PC 配合。污染会话下的复位载荷完整性不在本 Phase
(已由 E4 bench 60 轮实证),本 Phase 只验状态机跑通与判据语义。
@@ -1115,7 +1115,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON
| TC-NET-1502b | LISTEN 态 Socket 复位(GRACEFUL,走 WAIT_CLOSE 二段补发路径) | ≤3000ms 落定 rc=0(实测 ~180ms);复位后 CLOSED |
| TC-NET-1503 | 链路态缓存 + 事件护栏边界 | 稳定链路 feed 两次不虚增变化计数;link_up 缓存=1;`sock_events(8)` 越界返 0 |
-**二段关闭语义**(本 Phase 首跑实证,详见实验方案 E2 补充发现):LISTEN 态
+**二段关闭语义**(本 Phase 首跑实证,详见 `docs/网络/网络自愈架构设计.md` §5.2 E2):LISTEN 态
`CLOSE_SOCKET` 返 SUCCESS 但只拆监听(TCP 01→00),byte0 恒 05;需再发一次
CLOSE 才落 00。与 TRAP-NET-07 的 `0x05/0x00` 驻留签名同源;`sock_reset_poll` 的
WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 TRAP-NET-09 open 重试吸收。
diff --git a/docs/测试说明/模拟器差距清单.md b/docs/测试说明/模拟器差距清单.md
index f7f4266d..ec5d89c7 100644
--- a/docs/测试说明/模拟器差距清单.md
+++ b/docs/测试说明/模拟器差距清单.md
@@ -42,10 +42,10 @@
|---|---|---|---|---|---|
| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;N-4 二期专门建模 |
| N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) |
-| N-3 | ~~`net_recv_to` 缺失~~(**2026-10-13 N34 阶段 1.5 已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 |
-| N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten`/pump 自动重建(N34 阶段1 后无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` |
+| N-3 | ~~`net_recv_to` 缺失~~(**2026-10-13 自愈收敛阶段 1.5 已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 |
+| N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten`/pump 自动重建(自愈收敛阶段1 后无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` |
| N-5 | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 |
-| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(N34 阶段 1.5:`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + L3 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(N-1)仍不建模 | —— |
+| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(自愈收敛阶段 1.5:`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + L3 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(N-1)仍不建模 | —— |
### 2.2 日志与配置
@@ -109,5 +109,5 @@
| 日期 | 变更 |
|---|---|
-| 2026-10-13 | **N34 阶段 1.5 recv 语义主流化**:`net_socket_sim.c` 镜像 `net_recv_nb` / `net_recv_to` / `net_setblocking`,阻塞语义改按控制块 `blocking` 属性在包装层模拟(Winsock 句柄仍恒 FIONBIO)——N-3 的 `net_recv_to` 缺失部分**已消除**(划线留档),N-6 改写为"模式属性 + 包装层模拟";内部挂起模型(N-1)不变 |
+| 2026-10-13 | **自愈收敛阶段 1.5 recv 语义主流化**:`net_socket_sim.c` 镜像 `net_recv_nb` / `net_recv_to` / `net_setblocking`,阻塞语义改按控制块 `blocking` 属性在包装层模拟(Winsock 句柄仍恒 FIONBIO)——N-3 的 `net_recv_to` 缺失部分**已消除**(划线留档),N-6 改写为"模式属性 + 包装层模拟";内部挂起模型(N-1)不变 |
| 2026-10-02 | 初版(S13):收录 6 条网络差距(N-1~N-6)、4 条日志/配置差距(L-1~L-4)、7 条硬件专属差距(H-1~H-7)、4 条"有意对齐"项。证据来源:`net_socket_sim.c` 文件头设计要点 + 双端导出函数逐一对比(MCU 有而 sim 缺 9 个)+ 各子系统文档既有记载汇总。§3 给出 6 条"必须回真机"决策规则 |
diff --git a/docs/测试说明/链路延迟测试说明.md b/docs/测试说明/链路延迟测试说明.md
index 3c0ecee1..b9de1881 100644
--- a/docs/测试说明/链路延迟测试说明.md
+++ b/docs/测试说明/链路延迟测试说明.md
@@ -91,7 +91,7 @@ sequenceDiagram
| TCP 重传 | **8 次 × 200ms = 1.6s 窗口**(固定周期,非 RTT 自适应;P0-a 2026-09-22 实装,原 8×500ms=4s) | `NET_RETRANS_COUNT/PERIOD_MS`(`net_config.h`) |
| SPI2 速率 | APB1 42MHz ÷ 8 ≈ **5.25 Mbit/s** → 1KB ≈ **1.6ms**(分频 8 为 PCB 约束,勿"优化") | `Src/spi.c` |
| 发送等待超时 | 1s(`NET_SEND_TIMEOUT_MS`) | `net_config.h` |
-| 接收超时 | **无默认预算**(N34 阶段 1.5:`net_recv` 无限阻塞,超时归调用方——`net_select` / `net_recv_to`;原 30s `NET_RECV_TIMEOUT_MS` 已删) | `net_config.h`(宏已移除) |
+| 接收超时 | **无默认预算**(自愈收敛阶段 1.5:`net_recv` 无限阻塞,超时归调用方——`net_select` / `net_recv_to`;原 30s `NET_RECV_TIMEOUT_MS` 已删) | `net_config.h`(宏已移除) |
| 调试串口 | USART1 115200,**逐字节阻塞发送**(`fputc` = `HAL_UART_Transmit` 单字节) | `Src/usart.c` |
| LVGL 刷新周期 / 色深 | 20ms / 1bpp(`RENDER_MODE_FULL`) | `lv_conf.h`、`ui_port.c` |
@@ -669,7 +669,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
| **开机首个 FTP 会话第 2 个数据连接 425** | **D20**(已定位未修,§8.2) | 复现:`test/host/ftp_regression.py`(复位开边界);现场规避:客户端重试 425(瞬态 4xx);根治方向:数据 Socket 轮换 |
| **`[LAT]` 多段数值雷同 / 全 0 却 verdict=OK** | **D18/D19**(测试工装) | 核对 `lat_probe_add_seg` 段名过滤 + `lat_time_init` 调用 + `med==0` 判无效 |
| **连接建立后 15~30s 无流量即被 RST** | **D13**:芯片空闲超时(芯片行为) | 客户端须容忍并重连(peer `--wait` 重试是正常路径) |
-| **peer 重连后 MCU 状态滞留、新连接字节被吞** | **D14**:芯片静默回收 Socket | 对账⑤自动认领(N34 阶段1:手动 `net_resync_listen`/`net_relisten` 已退役,Phase 13 abort 期自愈亦交 net 层) |
+| **peer 重连后 MCU 状态滞留、新连接字节被吞** | **D14**:芯片静默回收 Socket | 对账⑤自动认领(自愈收敛阶段1:手动 `net_resync_listen`/`net_relisten` 已退役,Phase 13 abort 期自愈亦交 net 层) |
| L8 超而 L7 正常 | 生产路径附加成本(uiTask 节拍/LVGL/日志) | 对照 L10/L11 与 §10.1(渲染已排除;per-recv 日志已删,S8 非阻塞日志仍待办) |
| Δ 显著 >80ms | CH395F 栈净开销异常 | 回到 L1~L9 逐段排查 |
diff --git a/docs/网络/BSD_Socket_API_使用指南.md b/docs/网络/BSD_Socket_API_使用指南.md
index ab7b2221..88e5b147 100644
--- a/docs/网络/BSD_Socket_API_使用指南.md
+++ b/docs/网络/BSD_Socket_API_使用指南.md
@@ -41,7 +41,7 @@
| 特性 | Linux Socket | 本实现 |
|------|--------------|--------|
| Socket 数量 | 无限制 | 最多 8 个 |
-| 阻塞默认 | 默认阻塞(无隐藏超时) | **同**(N34 阶段 1.5:`net_recv()` 无限阻塞,超时归调用方——`net_select` / `net_recv_to`) |
+| 阻塞默认 | 默认阻塞(无隐藏超时) | **同**(自愈收敛阶段 1.5:`net_recv()` 无限阻塞,超时归调用方——`net_select` / `net_recv_to`) |
| 非阻塞模式 | `O_NONBLOCK` / `MSG_DONTWAIT` | `net_setblocking(fd, 0)`(模式属性)/ `net_recv_nb()` / `NET_MSG_DONTWAIT`(单次) |
| 错误码 | errno | `net_get_errno()` |
| 头文件 | `` | `"net_socket.h"` |
@@ -62,8 +62,8 @@
| `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) |
| `net_types.h` | 类型定义(地址结构、Socket 控制块等) |
| `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 |
-| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含 L3 pending 化 recv、TRAP-NET-17 周期对账 + 监听重建**合一** reconciler(`net_reconcile_tick`,100ms,分级去抖+冷却,形态①②③④⑤/U11,N34 阶段 3) |
-| `net_listener.c`(**真机专属**) | **监听生命周期**(N-32 §10 模块化):`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick`(D10 重试 30ms/2s)/ `net_listener_settle`(沉降/隔离 100ms/400ms);原 `net_listener_pump` 的重建决策已并入 `net_reconcile_tick` 形态③④(N34 阶段 3,单一 100ms tick);内部头,模拟器不编译 |
+| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含 L3 pending 化 recv、TRAP-NET-17 周期对账 + 监听重建**合一** reconciler(`net_reconcile_tick`,100ms,分级去抖+冷却,形态①②③④⑤/U11,自愈收敛阶段 3) |
+| `net_listener.c`(**真机专属**) | **监听生命周期**(N-32 §10 模块化):`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick`(D10 重试 30ms/2s)/ `net_listener_settle`(沉降/隔离 100ms/400ms);原 `net_listener_pump` 的重建决策已并入 `net_reconcile_tick` 形态③④(自愈收敛阶段 3,单一 100ms tick);内部头,模拟器不编译 |
| `net_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现(仅 MCU 端编译) |
| `net_socket_sim.c`(`simulator/sim/`) | Winsock2 后端(模拟器端,与 `net_socket.c` 互斥编译),详见 §2.3 |
@@ -81,7 +81,7 @@ PC 模拟器(`simulator/`,CMake + SDL2)与 MCU 端共用同一份上层网
| 底层后端 | CH395F(SPI2) | Winsock2(链接 `ws2_32`) |
| `net_init()` | 芯片初始化 / 复位 / 配置 IP·MAC / 分配 Socket 缓冲 | 仅确保 `WSAStartup`(幂等);ip/mask/gw/mac 四参数全部忽略 |
| fd 模型 | 控制块索引 0~7 | 相同(并行数组 `s_ws[]` 存 Winsock 句柄,不侵入共享的 `net_sock_t`) |
-| 并发 / 阻塞 | 消息队列 + `netTask` 串行化 | 单线程直调;Winsock 句柄创建即非阻塞(FIONBIO),阻塞语义按控制块 `blocking` 属性在包装层模拟(`net_recv` 轮询等待 / `net_recv_nb` 立即返回,N34 阶段 1.5 与 MCU 端对齐) |
+| 并发 / 阻塞 | 消息队列 + `netTask` 串行化 | 单线程直调;Winsock 句柄创建即非阻塞(FIONBIO),阻塞语义按控制块 `blocking` 属性在包装层模拟(`net_recv` 轮询等待 / `net_recv_nb` 立即返回,自愈收敛阶段 1.5 与 MCU 端对齐) |
| `net_poll()` | CH395F 中断轮询 + 状态机更新 | 空操作(返回 0,非阻塞 I/O 直接反映事件) |
| `net_accept()` | 单连接:监听 Socket 自身即数据通道(返回监听 Socket 自身) | Winsock 语义:返回独立于监听 Socket 的新 fd(监听 Socket 保持 `LISTENING`) |
| 事件回调 | `net_poll` 检测后主动触发 | 仅存储,不主动触发(上层自行非阻塞轮询感知) |
@@ -155,7 +155,7 @@ sequenceDiagram
| 规则 | 说明 |
|------|------|
-| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(N34 阶段1:`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
+| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(自愈收敛阶段1:`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
| **`netTask` 内** | 内部 API `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` / `net_listen_locked()` |
| **`net_socket()` / `net_bind()`** | 直接调用,无需队列;实际在就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)中创建监听 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **3 个非阻塞 tick**(D10 重监听重试 → L3 挂起 recv 完成 → TRAP-NET-17 对账+监听重建合一 reconciler 100ms),再处理队列 |
@@ -177,19 +177,19 @@ int net_close_sock(net_sock_t *p_sock);
- **队列句柄全局可见**:通过 `extern osMessageQueueId_t netMsgQueueHandle` 引用
- **`netTask` 不可向自身发消息**:`netTask` 中直接调 `net_send()` 会死锁(等待自己处理消息),必须在 `netTask` 中使用 `net_send_sock()` 等内部版本
- **阻塞 recv 不再独占 netTask(L3 pending 化,2026-09-22)**:无数据时公共 `net_recv()` / `net_recv_to()` **登记挂起请求后返回**(每 fd 至多一个,禁止覆盖——覆盖即丢失前调用方通知 = 永久挂死);完成由 `net_pending_tick()` 按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方——等待期间调用方在自己上下文睡通知,netTask 零占用(消除 E-02 头阻塞)。副作用:`buf` 指针须保持有效至返回;close / 网络重建 / 陈旧重同步会**显式取消并通知**(`NET_ERR_CONNRESET` 等),调用方永不挂死
-- **recv 语义主流化(N34 阶段 1.5,2026-10-13)**:对齐 POSIX/Python——**模式归使用者、超时归调用方、API 不强加默认预算**。`net_recv()` 默认**无限阻塞**(`NET_RECV_TIMEOUT_MS` 30s 默认已删),唤醒源 = 数据 / EOF / close / 网络重建(CONNRESET),会话死亡由对账① ~400ms 判死兜底;非阻塞具名入口 `net_recv_nb()`(原 `NET_MSG_DONTWAIT` 路径转正),模式属性 `net_setblocking(fd, on)` 挂 socket 控制块;超时首选 `net_select(...)`,`net_recv_to(...)` 保留为**显式预算**便捷入口。内部看门狗降级为 **tripwire**:挂起超 60s 只 `[ERR]` 留痕一条(完成逻辑缺位探针),不再伪造 TIMEDOUT 返回值。⚠ **文档契约(嵌入式特有)**:IWDG `required_mask` 内任务**禁止无限阻塞 recv**——必须 `net_select` 超时或 `net_recv_to` 显式预算("超时归使用者"对受监控任务是义务不是选项)
+- **recv 语义主流化(自愈收敛阶段 1.5,2026-10-13)**:对齐 POSIX/Python——**模式归使用者、超时归调用方、API 不强加默认预算**。`net_recv()` 默认**无限阻塞**(`NET_RECV_TIMEOUT_MS` 30s 默认已删),唤醒源 = 数据 / EOF / close / 网络重建(CONNRESET),会话死亡由对账① ~400ms 判死兜底;非阻塞具名入口 `net_recv_nb()`(原 `NET_MSG_DONTWAIT` 路径转正),模式属性 `net_setblocking(fd, on)` 挂 socket 控制块;超时首选 `net_select(...)`,`net_recv_to(...)` 保留为**显式预算**便捷入口。内部看门狗降级为 **tripwire**:挂起超 60s 只 `[ERR]` 留痕一条(完成逻辑缺位探针),不再伪造 TIMEDOUT 返回值。⚠ **文档契约(嵌入式特有)**:IWDG `required_mask` 内任务**禁止无限阻塞 recv**——必须 `net_select` 超时或 `net_recv_to` 显式预算("超时归使用者"对受监控任务是义务不是选项)
- **队列阻塞语义**:只有 `osMessageQueuePut` 失败(8 长度固定队列,极端情形)时公共 API 阻塞;recv 挂起等待**不计入**队列占用(完成前队列中无消息滞留)
- **哪些操作要求 netTask 已运行**:`net_listen()` / `net_accept()` / `net_connect()` / `net_send()` / `net_recv()` / `net_close()` 全经消息队列 → 只能在就绪屏障后的任务调用;`net_socket()` / `net_bind()` 为直接调用,但同样建议屏障后;**`net_listener_armed()` / `net_can_read()` 是纯 RAM 读控制块**(§10.1),不入队、不等回执,**任意任务可安全周期调用**(用途 = 门控,把"无谓的 RPC"降为 0)
### 3.6 芯片/本层状态对账与重监听自愈(TRAP-NET-09/-17/-18)
-单连接模型下"监听 Socket = 数据通道",多种芯片行为会造成**芯片侧与本层状态失同步**,纯事件(中断)驱动无法自愈。当前版本有**四层自愈**(N34 阶段 3:原 200ms 周期对账与 100ms 监听器 pump 合并为**单一 100ms `net_reconcile_tick`**),全在 netTask 内、全非阻塞、不冻结主循环:
+单连接模型下"监听 Socket = 数据通道",多种芯片行为会造成**芯片侧与本层状态失同步**,纯事件(中断)驱动无法自愈。当前版本有**四层自愈**(自愈收敛阶段 3:原 200ms 周期对账与 100ms 监听器 pump 合并为**单一 100ms `net_reconcile_tick`**),全在 netTask 内、全非阻塞、不冻结主循环:
| 机制 | 触发 | 动作 | 关键参数 |
|---|---|---|---|
| **`auto_relisten`** | 数据通道正常 EOF / DISCONNECT | 重开 + 重新 LISTEN,回 LISTENING;应用层无需 close/重 listen | `net_listen()` 置位;`net_close()` 清 |
| **D10 非阻塞重监听重试** | 重开失败(TRAP-NET-09:芯片 close 后需沉降) | 单次尝试失败即登记待重试,每轮到点重试;超时限放弃并记 `relisten give up` | 重试间隔 30ms / 时限 2s(自首次尝试);同 fd 有在途挂起 recv 时跳过(L3 保险,防毁在途连接) |
-| **TRAP-NET-17 周期对账 + 监听重建(合一 `net_reconcile_tick`,N34 阶段 3;对账①②⑤ + 原 pump③④/U11)** | 芯片丢 CONNECT/DISCONNECT 中断(快速复连);静默回收 Socket(peer FIN,无 DISCONNECT 中断);监听型 fd 芯片监听失位 / 本层状态滞后 | 每 100ms 对在用 socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs),修正**五类**失同步 + 一类被动重同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理(只置事实);② 本层 LISTENING 而芯片已握手 → 补 accept(N-30 幽灵甄别);③ 本层 LISTENING 而芯片监听失位 → 重建(`net_listener_try`);④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(N34 阶段1 新增,原手动 API `net_resync_listen` 的语义并入);U11 本层 CLOSED 而芯片**正在** LISTEN → 只做状态重同步、不发芯片命令 | 周期 100ms;**分级去抖**:②=1 轮、③④⑤=2 轮、①=4 轮(M3 让位窗 400ms,**勿改小**)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴;U11 被动重同步不去抖、不占冷却);其它位置只置事实、不做重建动作 |
+| **TRAP-NET-17 周期对账 + 监听重建(合一 `net_reconcile_tick`,自愈收敛阶段 3;对账①②⑤ + 原 pump③④/U11)** | 芯片 CONNECT/DISCONNECT 事件未收到(快速复连,读清消费竞态——E14 复核:非芯片侧丢失);静默回收 Socket(peer FIN,无 DISCONNECT 事件);监听型 fd 芯片监听失位 / 本层状态滞后 | 每 100ms 对在用 socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs),修正**五类**失同步 + 一类被动重同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理(只置事实);② 本层 LISTENING 而芯片已握手 → 补 accept(N-30 幽灵甄别);③ 本层 LISTENING 而芯片监听失位 → 重建(`net_listener_try`);④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(自愈收敛阶段1 新增,原手动 API `net_resync_listen` 的语义并入);U11 本层 CLOSED 而芯片**正在** LISTEN → 只做状态重同步、不发芯片命令 | 周期 100ms;**分级去抖**:②=1 轮、③④⑤=2 轮、①=4 轮(M3 让位窗 400ms,**勿改小**)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴;U11 被动重同步不去抖、不占冷却);其它位置只置事实、不做重建动作 |
| **TRAP-NET-18 排渣隔离** | 异常释放(芯片 TIMEOUT,如客户端中止上传):在途数据落进 RX 缓冲,直接复监听 → 下条连接把 ~2048B 残渣当自己的数据读走(上传静默截断);正常 FIN 释放亦有残留 DISCONNECT 收尾问题 | 复监听前沉降/隔离:持续排空 RX 残渣 + 主动 close + 排空中断,等芯片回 CLOSED/TCP_CLOSED 才 open+listen;到期强制放行 | 沉降等级(`net_listener_settle`):异常释放 QUARANTINE **400ms** / 正常释放 GRACEFUL **100ms**(2026-09-24 起正常 FIN 亦需沉降);上限 **800ms** 强制放行(**必须 < 2s 时限**,否则监听被放弃) |
**行为结论**:监听器恢复从"一次性事件"变为"登记 → 到点重试"过程:
@@ -247,7 +247,7 @@ int main(void)
- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT)
- 触发事件回调
- 处理数据接收
- - `net_process_messages()` 每轮先跑 3 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 唤醒,E-02 修复)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,丢中断/静默回收/监听失位自愈,§3.6)
+ - `net_process_messages()` 每轮先跑 3 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 唤醒,E-02 修复)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,事件未收到/静默回收/监听失位自愈,§3.6)
- **监听 Socket 建立**:由就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)经队列 API 建立(`net_socket` → `net_bind` → `net_listen`);断连后 net 层自动重监听(§3.6),服务层只需循环 `net_accept`,无需维护重监听逻辑
```c
@@ -875,7 +875,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
| `addrlen` | 输入输出:地址长度 |
| 返回值 | 新连接描述符(单连接模式下 == 监听 `sockfd`),无连接时返回 -1(可循环重试) |
-> 单连接模式:监听 Socket 在 CONNECT 后**自身即数据通道**(§5.1)。`net_accept` 失败时若状态为 CLOSED 且 `auto_relisten` 开启,内部触发**非阻塞**重监听(失败交重试轮询,§3.6)。中断丢失也不会让监听死亡——合一对账(100ms 周期,§3.6)在 ~200ms 内修正"本层 LISTENING 而芯片监听消失"等失同步。
+> 单连接模式:监听 Socket 在 CONNECT 后**自身即数据通道**(§5.1)。`net_accept` 失败时若状态为 CLOSED 且 `auto_relisten` 开启,内部触发**非阻塞**重监听(失败交重试轮询,§3.6)。事件未收到也不会让监听死亡——合一对账(100ms 周期,§3.6)在 ~200ms 内修正"本层 LISTENING 而芯片监听消失"等失同步。
#### net_listener_armed / net_can_read - 就绪门控(**纯 RAM 读,不走消息队列**)
@@ -887,10 +887,10 @@ int net_can_read(int sockfd); /* 1 = 应发一次 recv;0 = 可跳过本
| 项 | 说明 |
|------|------|
| 代价 | **零 RPC**:只读 Socket 控制块(`net_get_sock()`),不入队、不等 netTask 回执 → 任意任务上下文可安全周期调用,**不被 netTask 忙碌程度拖住** |
-| `net_listener_armed` 判据 | `ESTABLISHED`(CONNECT 已处理、待应用 accept——单连接模型的"连接已到"电平,漏放行则任务周期慢的消费者永不 accept),或 `LISTENING`,或 `CLOSED + auto_relisten` **且 RX 有残留**(closed-with-data 救回窗)——表达"这个监听器还在服役、这一轮值不值得发起 accept",而非"一定有连接"。(前身 `net_can_accept()` 只认 `ESTABLISHED`,会把监听服役窗口全挡掉、丢 CONNECT 探针从不运行——N34 阶段1 已退役,其 ESTABLISHED 放行态并入本函数,勿再引入) |
+| `net_listener_armed` 判据 | `ESTABLISHED`(CONNECT 已处理、待应用 accept——单连接模型的"连接已到"电平,漏放行则任务周期慢的消费者永不 accept),或 `LISTENING`,或 `CLOSED + auto_relisten` **且 RX 有残留**(closed-with-data 救回窗)——表达"这个监听器还在服役、这一轮值不值得发起 accept",而非"一定有连接"。(前身 `net_can_accept()` 只认 `ESTABLISHED`,会把监听服役窗口全挡掉、丢 CONNECT 探针从不运行——自愈收敛阶段1 已退役,其 ESTABLISHED 放行态并入本函数,勿再引入) |
| `net_can_read` 判据 | `ESTABLISHED` → 看 `readable`;**非 `ESTABLISHED` 一律返 1**(必须让上层 recv 一次拿到 EOF/错误并复位自身状态,门掉它会把"连接已消失"永久藏住);描述符失效也返 1(让 recv 报 BADF 走既有错误分支) |
| 用途 | **门控**:无活时跳过 `net_accept()`/`net_recv()`——两者内部都是 `osMessageQueuePut(osWaitForever)` + `xTaskNotifyWait(portMAX_DELAY)`,**均无上界**(队列深 8 且与 FTP/timeSync 共享) |
-| ⚠ 不可作为唯一判据 | `readable` 只由 RECV_OK 中断置位,而 §3.6 的对账**不含**"芯片 RX 非空但 `readable=0`" → 中断丢失时 `net_can_read()` 恒返 0。**调用方必须保留低频兜底 recv**(`ui_net_sink.c` 每 32 轮无条件收一次,教训见 `docs/子系统设计/看门狗设计说明.md` §11.5 第 1 条) |
+| ⚠ 不可作为唯一判据 | `readable` 只由 RECV_OK 中断置位,而 §3.6 的对账**不含**"芯片 RX 非空但 `readable=0`" → 事件未收到时 `net_can_read()` 恒返 0。**调用方必须保留低频兜底 recv**(`ui_net_sink.c` 每 32 轮无条件收一次,教训见 `docs/子系统设计/看门狗设计说明.md` §11.5 第 1 条) |
| 竞态 | netTask 是唯一写者,单字读;最坏"晚一轮发现"(netTask 每 1 ms 就 `net_poll()`)或"扑空"(RPC 返回 `NET_ERR_WOULDBLOCK`),均无副作用 |
| 模拟器端 | `net_socket_sim.c` 同名实现**恒返回 1**——Winsock 本就非阻塞、无 RPC 成本,恒 1 使门控在模拟器上逐位等价于改前行为 |
@@ -935,7 +935,7 @@ int net_recv(int sockfd, void *buf, int len, int flags);
| `flags` | 0=按 socket 阻塞属性等待,`NET_MSG_DONTWAIT`=单次强制非阻塞 |
| 返回值 | 实际接收字节数 (>0);`0`=对端关闭(net 层随即自动重监听);`-1`=错误(非阻塞无数据=`NET_ERR_WOULDBLOCK`;close/网络重建/陈旧重同步取消=`NET_ERR_CONNRESET`;**阻塞形态无"预算内无数据"一档——默认无限等待,超时是调用方策略**) |
-> **语义主流化(N34 阶段 1.5,2026-10-13)**:对齐 POSIX/Python——`net_recv()` 默认**无限阻塞**(旧 `NET_RECV_TIMEOUT_MS` 30s 默认预算已删,API 不再强加隐藏值)。唤醒源 = 数据 / EOF / close / 网络重建(`NET_ERR_CONNRESET`);会话死亡由对账①在 ~400ms 内判死兜底(§3.6),不存在"对端消失后永久挂死"窗口。需要"等但不无限等":首选 `net_select(r,w,e,timeout_ms)`(§7.1),或 `net_recv_to()` 显式预算。
+> **语义主流化(自愈收敛阶段 1.5,2026-10-13)**:对齐 POSIX/Python——`net_recv()` 默认**无限阻塞**(旧 `NET_RECV_TIMEOUT_MS` 30s 默认预算已删,API 不再强加隐藏值)。唤醒源 = 数据 / EOF / close / 网络重建(`NET_ERR_CONNRESET`);会话死亡由对账①在 ~400ms 内判死兜底(§3.6),不存在"对端消失后永久挂死"窗口。需要"等但不无限等":首选 `net_select(r,w,e,timeout_ms)`(§7.1),或 `net_recv_to()` 显式预算。
>
> **L3 pending 化(2026-09-22)**:无数据时阻塞接收**不占 netTask**——登记挂起请求(每 fd 至多一个,禁止覆盖:覆盖会丢失前调用方通知 = 永久挂死)后**立即返回**;完成按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方。等待期间调用方在自己上下文睡通知,netTask 全程零占用(消除 E-02 头阻塞)。内部 tripwire(原看门狗降级):挂起超 60s 只 `[ERR]` 留痕一条("完成逻辑缺位"探针),**绝不伪造 TIMEDOUT 返回值**。
>
@@ -952,7 +952,7 @@ int net_recv_nb(int sockfd, void *buf, int len);
| `sockfd` / `buf` / `len` | 同 `net_recv` |
| 返回值 | >0 字节数;`0`=对端关闭;`-1`=无数据(`NET_ERR_WOULDBLOCK`)或真错误(`net_get_errno` 可查) |
-原 `NET_MSG_DONTWAIT` 路径转正(N34 阶段 1.5):等价 `net_recv(sockfd, buf, len, NET_MSG_DONTWAIT)`,单次强制非阻塞、不改变 socket 的 blocking 属性。受 IWDG 监控 / 有周期职责的任务的推荐默认入口(生产用户:`ui_net_sink` :7003、`iec104_serv` :2404)。
+原 `NET_MSG_DONTWAIT` 路径转正(自愈收敛阶段 1.5):等价 `net_recv(sockfd, buf, len, NET_MSG_DONTWAIT)`,单次强制非阻塞、不改变 socket 的 blocking 属性。受 IWDG 监控 / 有周期职责的任务的推荐默认入口(生产用户:`ui_net_sink` :7003、`iec104_serv` :2404)。
#### net_setblocking - 设置阻塞模式属性
@@ -977,7 +977,7 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
| 参数 | 说明 |
|------|------|
| `sockfd` / `buf` / `len` | 同 `net_recv` |
-| `timeout_ms` | 本片等待预算(ms,**必须 >0**;0 = `net_recv` 的无限等待语义,恰与本接口"有界"存在理由相反,直接拒 `NET_ERR_INVAL`——N34 阶段 1.5 起本接口是**显式预算**入口,不再有"默认 30s"隐藏值) |
+| `timeout_ms` | 本片等待预算(ms,**必须 >0**;0 = `net_recv` 的无限等待语义,恰与本接口"有界"存在理由相反,直接拒 `NET_ERR_INVAL`——自愈收敛阶段 1.5 起本接口是**显式预算**入口,不再有"默认 30s"隐藏值) |
| 返回值 | >0=字节数(允许部分读,TCP 分段);`0`=对端关闭(排空残留后);`-1`=真错误(`net_get_errno` 可查);**-2=本片预算内无数据**(**不是 errno**,不得依赖 errno 判超时——`s_net_errno` 是跨任务共享全局量) |
调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(E-02 头阻塞修复,完整例 §11.2);受 IWDG 监控任务的长等待必须走本接口(或 `net_select`)+ 每片喂心跳。
@@ -1004,13 +1004,13 @@ int net_close(int sockfd);
释放 Socket 控制块,TCP 先断开连接。⚠ **单连接模型(MCU 端 CH395F)不得对监听 fd 调本 API**——连接与监听同一 fd,close 会连监听一起毁掉(§12.4);EOF 时 net 层已 `auto_relisten`,应用层**不得**在 EOF 时 close 该 fd。"放弃会话但保监听"用下面的 `net_drop_conn()`。
-#### net_drop_conn - 主动拆会话、保留监听(N34 阶段 3,E-02 收口)
+#### net_drop_conn - 主动拆会话、保留监听(自愈收敛阶段 3,E-02 收口)
```c
int net_drop_conn(int sockfd);
```
-应用**放弃会话**(对时 30s 预算耗尽、协议超时)时主动拆掉当前 TCP 会话并**保留监听**——单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(PC 内核自动 ACK keepalive,芯片僵尸判不死,定案见 `docs/网络/N34-自愈审计.md` §6 E13),而应用又不得 `net_close` 监听 fd(毁监听);本 API 补上"拆连接不拆监听"的应用侧形态。
+应用**放弃会话**(对时 30s 预算耗尽、协议超时)时主动拆掉当前 TCP 会话并**保留监听**——单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(PC 内核自动 ACK keepalive,芯片僵尸判不死,定案见 `docs/网络/网络自愈架构设计.md` §5.2 E13),而应用又不得 `net_close` 监听 fd(毁监听);本 API 补上"拆连接不拆监听"的应用侧形态。
- **netTask 侧**:先了结本 fd 挂起 recv(排空/取消 + 必通知),再 `sock_reset` QUARANTINE(FIN + 排渣 + 排空事件 + 等 byte0=00H,TRAP-NET-18 异常释放语义,400ms 底);本层收口 CLOSED;`auto_relisten` fd 随即 `net_listener_try` 即时重监听(失败交 D10 重试 + reconciler 形态④)
- **幂等**:无活动会话(非 ESTABLISHED/CONNECTING)直接返 0,不动监听
@@ -1029,12 +1029,12 @@ int net_sock_state(int sockfd);
跨任务只读快照(单字节读);描述符无效/未占用返回 -1;值可能滞后 netTask 最新状态。用于**状态轮询**(客户端等 ESTABLISHED,§5.2),不替代 accept/connect。
-**net_resync_listen / net_relisten — 已退役(N34 阶段1,勿再引入)**
+**net_resync_listen / net_relisten — 已退役(自愈收敛阶段1,勿再引入)**
两者曾是"应用层手动自愈"入口:`net_resync_listen()` 只修 MCU 侧状态(D14 静默回收),`net_relisten()` 无条件芯片侧 open+listen。现状:
- **静默回收**由对账⑤自动认领(§3.6:本层 ESTABLISHED 而芯片 0x05/0x01 → RESYNC 语义,≤400ms 去抖 + 1s 冷却,纪律"不发芯片命令"原样保留在认领动作内)——应用无需判定"连接是否已死",也就消除了"误判活连接调 resync 把活连接标成监听态"的 footgun。
-- **监听重建**由 reconciler 形态③④(100ms 周期,唯一重建入口;N34 阶段 3 起原 pump 已并入 `net_reconcile_tick`)/ D10(30ms 重试)单一所有者负责——`net_relisten()` 对健康监听器反而是 footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。
+- **监听重建**由 reconciler 形态③④(100ms 周期,唯一重建入口;自愈收敛阶段 3 起原 pump 已并入 `net_reconcile_tick`)/ D10(30ms 重试)单一所有者负责——`net_relisten()` 对健康监听器反而是 footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。
生产调用者本就为零(仅板级测试 Phase 13,已改由自动机制接管)。历史实现见 `net_socket.c` git 历史。
@@ -1057,10 +1057,10 @@ void net_process_messages(void);
处理其他任务投递的请求(send/recv/connect/accept/close/drop_conn)。每次调用先跑 3 个**非阻塞** tick,再取队列消息(0 超时出队):
1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6)
-2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ tripwire:挂起超 60s 只 `[ERR]` 留痕一次,不强制了结(N34 阶段 1.5)
+2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ tripwire:挂起超 60s 只 `[ERR]` 留痕一次,不强制了结(自愈收敛阶段 1.5)
3. **TRAP-NET-17 芯片/本层对账 + 监听重建(合一 `net_reconcile_tick`)**:100ms 周期,判据读驱动状态缓存(`ch395f_sock_state_cached`),修正**五类**失同步(①②③④⑤)+ U11 被动重同步——原 200ms 对账与 100ms 监听器 pump 已合并为单一 tick,**唯一重建入口**=形态③④(§3.6)
-> `net_msg_t.timeout_ms`:L3 显式预算(`net_recv_to()`),0=**无限等待**(N34 阶段 1.5 删除 `NET_RECV_TIMEOUT_MS` 30s 默认预算——超时归调用方)。消息类型 7 种(send/recv/close/connect/listen/accept/drop_conn)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(N34 阶段1)。
+> `net_msg_t.timeout_ms`:L3 显式预算(`net_recv_to()`),0=**无限等待**(自愈收敛阶段 1.5 删除 `NET_RECV_TIMEOUT_MS` 30s 默认预算——超时归调用方)。消息类型 7 种(send/recv/close/connect/listen/accept/drop_conn)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(自愈收敛阶段1)。
#### net_send_sock - 发送数据(内部版本)
@@ -1204,7 +1204,7 @@ void tcp_client_reconnect(void)
net_send(sock, "heartbeat", 9, 0);
}
- /* 检查接收(N34 阶段 1.5:非阻塞具名入口 net_recv_nb) */
+ /* 检查接收(自愈收敛阶段 1.5:非阻塞具名入口 net_recv_nb) */
char buf[128];
int len = net_recv_nb(sock, buf, sizeof(buf) - 1);
if (len > 0) {
@@ -1275,7 +1275,7 @@ static int read_time_frame(int conn_sock, uint8_t buf[FRAME_LEN]) {
**是的**,且 `net_poll()` 与 `net_process_messages()` 缺一不可:
- `net_poll()`:GPIO 轮询 INT#,**每轮批处理一批**中断,更新各 Socket 状态、触发事件回调(RECV 为电平触发,每轮只处理一批,避免饿死主循环)。
-- `net_process_messages()`:除串行处理队列请求外,每轮先跑 3 个**非阻塞**检查——D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 的唤醒)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,丢中断/静默回收/监听失位自愈,§3.6)。
+- `net_process_messages()`:除串行处理队列请求外,每轮先跑 3 个**非阻塞**检查——D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 的唤醒)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,事件未收到/静默回收/监听失位自愈,§3.6)。
建议主循环周期 ≤ 10ms(本项目 1ms)。若 netTask 主循环停摆(断点、任务被挂起),监听器不会恢复、阻塞 recv 不会完成——这是软件侧"监听死机"的最常见原因。
@@ -1304,7 +1304,7 @@ if (len <= 0) {
}
```
-**方式二:非阻塞接收(`net_recv_nb`,N34 阶段 1.5 转正)**
+**方式二:非阻塞接收(`net_recv_nb`,自愈收敛阶段 1.5 转正)**
```c
int len = net_recv_nb(sock, buf, sizeof(buf));
if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
@@ -1335,7 +1335,7 @@ if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
**芯片静默回收(D14,2026-09-22 台架实证)**:对端发 FIN 后,芯片可能**自行把 Socket 回收回 LISTEN 且不发 DISCONNECT 中断** → 本层滞留 ESTABLISHED,应用继续往死连接上读写,新连接的字节被旧会话吞掉。
-**无需应用处理(N34 阶段1 起)**:对账⑤自动认领此形态(§3.6:2 轮去抖 ≈200ms + 1s 冷却)→ RESYNC 语义只修本层状态 + 取消本 fd 挂起 recv,**不发任何芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒,run8 实证 170s 拒绝)。等待期间该端口实际可接新连接(芯片侧已 LISTEN),新连接经对账补 accept 被本层感知。原显式 API `net_resync_listen(fd)` 已退役——"应用判定连接已死"这一步本身就是误判源(对活连接调用会把活连接标成监听态)。
+**无需应用处理(自愈收敛阶段1 起)**:对账⑤自动认领此形态(§3.6:2 轮去抖 ≈200ms + 1s 冷却)→ RESYNC 语义只修本层状态 + 取消本 fd 挂起 recv,**不发任何芯片命令**(发 close/重监听会摧毁芯片侧健康监听器 → 连接永拒,run8 实证 170s 拒绝)。等待期间该端口实际可接新连接(芯片侧已 LISTEN),新连接经对账补 accept 被本层感知。原显式 API `net_resync_listen(fd)` 已退役——"应用判定连接已死"这一步本身就是误判源(对活连接调用会把活连接标成监听态)。
⚠ 切勿用**单次**芯片状态读决定重建:单次读可能落在"刚 open 未生效"瞬态,曾致监听反复重建直至 `relisten give up`(2026-09-23 实测风暴,三服务口全死)——任何重建动作必须去抖(连续确认)+ 冷却(§3.6)。
@@ -1442,11 +1442,11 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
### 12.10 监听器自愈行为与沉降/隔离(D10 / TRAP-NET-09、TRAP-NET-17、TRAP-NET-18 / N-32)
-**默认行为**(无需应用代码):断连 → `auto_relisten` 重开监听;重开失败 → 30ms 节奏重试(2s 时限);芯片丢中断/静默回收/监听失位 → 单一 reconciler 100ms 自愈(分级去抖,N34 阶段 3 起对账与 pump 合一);中止复连首上传 → 沉降/隔离。全在 netTask 内,应用只需保证 `net_poll()` + `net_process_messages()` 主循环在跑。
+**默认行为**(无需应用代码):断连 → `auto_relisten` 重开监听;重开失败 → 30ms 节奏重试(2s 时限);芯片事件未收到/静默回收/监听失位 → 单一 reconciler 100ms 自愈(分级去抖,自愈收敛阶段 3 起对账与 pump 合一);中止复连首上传 → 沉降/隔离。全在 netTask 内,应用只需保证 `net_poll()` + `net_process_messages()` 主循环在跑。
| 场景 | 修复前症状 | 机制 | 生产验证(2026-09-23) |
|---|---|---|---|
-| **TRAP-NET-17** 快速复连丢中断 → 监听静默死亡 | 待机后 UI `:7003` 快速复连 2/10 且**永久**拒连;FTP `:21` 100ms 间隔自第 29 轮死亡;设备日志**无报错**(状态静默滞留) | 单一 reconciler `net_reconcile_tick()`(100ms,对账①②⑤ + 重建③④/U11 合一,**唯一重建入口**=形态③④,N34 阶段 3;判据读驱动状态缓存)+ 分级去抖(②1 轮/③④⑤2 轮/①4 轮)+ 冷却(1s);`handle_disconnect_event` 只置沉降事实、不再自行重建 | `:7003` 10/10;`:21` 100ms/0ms/600ms 各 50/50;UI 20/20;FTP 正常 10/10(SHA-256);中止 RST/FIN 5/5 + 5/5;零重建风暴(2026-10-13 合并后复测 `:7003` 50/50、`:21` 三档各 50/50 全绿) |
+| **TRAP-NET-17** 快速复连事件面未收到 → 监听静默死亡(E14 复核:非芯片丢事件,是读清消费竞态) | 待机后 UI `:7003` 快速复连 2/10 且**永久**拒连;FTP `:21` 100ms 间隔自第 29 轮死亡;设备日志**无报错**(状态静默滞留) | 单一 reconciler `net_reconcile_tick()`(100ms,对账①②⑤ + 重建③④/U11 合一,**唯一重建入口**=形态③④,自愈收敛阶段 3;判据读驱动状态缓存)+ 分级去抖(②1 轮/③④⑤2 轮/①4 轮)+ 冷却(1s);`handle_disconnect_event` 只置沉降事实、不再自行重建 | `:7003` 10/10;`:21` 100ms/0ms/600ms 各 50/50;UI 20/20;FTP 正常 10/10(SHA-256);中止 RST/FIN 5/5 + 5/5;零重建风暴(2026-10-13 合并后复测 `:7003` 50/50、`:21` 三档各 50/50 全绿) |
| **TRAP-NET-18** 中止后首笔复连上传被静默截断 ≤2048B 并误报 226 成功 | RST/FIN 中止上传 → 紧接着的首笔上传"收"到 ~2KB 残留后判结束回 226 → 客户端以为成功 = **静默数据损坏**(第二笔起正常) | 异常释放(TIMEOUT)预重监听隔离:`net_drain_chip_rx()` 分块排空 RX 残渣 + 主动 close + 排空中断 + 等 CLOSED/TCP_CLOSED(400ms 起 / 800ms 上限 < 2s 时限) | RST 10/10、FIN 5/5 全通过;正常路径 30/30 + 20/20,SHA-256 全匹配 |
| **TC-NET-604**"接收冻结"虚警 | 旧判读脚本把"对端填满 2KB RX 窗口"误判为 STALL/WEDGE | **结论:非缺陷**(芯片 RX 缓冲即窗口,对端填满是常态);判据改为脚本层 STEP + 客户端内容校验(RET SHA-256),虚警工具归档(`MDK-ARM/retracted_bench_20260922/`,README 禁重跑) | 真实 FTP 路径 227 轮全通过(8KB×10 … 1MB×2);并发 UI 流 600/600 CRC;设备零 FAIL/ERROR/WARN |
@@ -1458,9 +1458,9 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
| 日期 | 变更 |
|------|------|
-| 2026-10-13 | **N34 阶段 3 自愈合并 + E-02 收口**(实施规格 `docs/网络/N34-自愈审计.md` §5.5):`net_listener_pump` **删除**,与 `net_reconcile_tick` 合并为**单一 100ms tick**(覆盖原对账①②⑤ + 原 pump③④/U11;判据读**驱动状态缓存** `ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs;**分级去抖** ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(M3 让位窗 400ms)+ 1s 冷却;§2.2 / §3.3 / §3.6(五层 → **四层**)/ §10.2 / §12.1 / §12.10 同步,主循环自愈 tick 由 4 个回到 **3 个**;常量 `NET_LISTENER_PUMP_PERIOD_MS` / `NET_LISTENER_STABLE_N` / `NET_LISTENER_COOLDOWN_MS` 删除);**忙等统一**——`net_listen_locked` / `net_connect_locked` open 失败重试"close+盲等 500ms"与 `net_close_locked` 500ms 排空循环改走驱动 `sock_reset` 原语(`net_sock_reset_sync`,GRACEFUL 100ms 底),`net_close_locked` 不再读清全局中断寄存器(F-NET-7:全局寄存器唯一读者 = `net_poll`);新增 **`net_drop_conn(sockfd)`**(§10.1,E-02 收口:拆会话**保监听**、幂等、EOF 路径不得调;新消息类型 `NET_MSG_DROP_CONN`;timeSync 30s 预算耗尽路径接入;模拟器实现为关数据连接);`net_poll` 每轮对在用 Socket 步进 `ch395f_sock_reset_poll`(同步包装 600ms 放弃后复位机不搁浅);`net_sock_state_t` 注释声明为"本层会话投影"(芯片事实源 = 驱动缓存,`net_types.h`);台架回归全绿(最终构建 Code=211348,E-02 双 PASS,见 `docs/网络/N34-自愈审计.md` §6 E-02 收口台架) |
-| 2026-10-13 | **N34 阶段 1.5 recv 语义主流化**(规格 `docs/网络/N34-自愈审计.md` §5.3.5):`net_recv()` 默认**无限阻塞**(删 `NET_RECV_TIMEOUT_MS` 30s 默认预算,唤醒源=数据/EOF/close/网络重建 CONNRESET);新增 `net_recv_nb()`(`NET_MSG_DONTWAIT` 路径转正)与 `net_setblocking(fd, on)`(模式挂 socket 控制块属性,`net_sock_t.blocking`,默认阻塞);`net_recv_to()` 保留为**显式预算**便捷入口(`timeout_ms=0` 语义从"默认 30s"改为"无限"故仍拒 INVAL);内部 recv 看门狗降级为 **tripwire**(挂起超 60s 只 `[ERR]` 留痕一次,不再"到点 +1s 强制了结"伪造 TIMEDOUT);⚠ 新增**文档契约**:IWDG `required_mask` 内任务禁止无限阻塞 recv(必须 `net_select` 超时或 `net_recv_to` 显式预算);`ui_net_sink` / `iec104_serv` 迁移 `net_recv_nb`,lftpd / timeSync 维持显式预算;模拟器 `net_socket_sim.c` 镜像三 API(blocking 属性包装层模拟) |
-| 2026-10-13 | **N34 阶段1 API 面收敛**(审计见 `docs/网络/N34-自愈审计.md` §4):删除手动自愈 API `net_relisten()` / `net_resync_listen()`(生产零调用;后者语义并入对账**⑤**——本层 ESTABLISHED 而芯片 0x05/0x01 静默回收 → RESYNC 只修本层,§3.6 对账由两类扩为三类①②⑤;前者与 pump/D10 重叠且对健康监听器是 footgun)与 `net_can_accept()`(只认 ESTABLISHED 的旧门控挡死丢 CONNECT 探针,监听门控统一 `net_listener_armed()`,§10.1 门控节改写);`NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 消息类型删除;timeSync 会话收尾不再 `net_close`(同 fd 续 accept,监听跨会话常驻);lftpd 应用层阻塞重试退役(就绪屏障已消除 TRAP-NET-09 启动形态) |
+| 2026-10-13 | **自愈收敛阶段 3 自愈合并 + E-02 收口**(实施规格现沉淀 `docs/网络/网络自愈架构设计.md` §4.1):`net_listener_pump` **删除**,与 `net_reconcile_tick` 合并为**单一 100ms tick**(覆盖原对账①②⑤ + 原 pump③④/U11;判据读**驱动状态缓存** `ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs;**分级去抖** ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(M3 让位窗 400ms)+ 1s 冷却;§2.2 / §3.3 / §3.6(五层 → **四层**)/ §10.2 / §12.1 / §12.10 同步,主循环自愈 tick 由 4 个回到 **3 个**;常量 `NET_LISTENER_PUMP_PERIOD_MS` / `NET_LISTENER_STABLE_N` / `NET_LISTENER_COOLDOWN_MS` 删除);**忙等统一**——`net_listen_locked` / `net_connect_locked` open 失败重试"close+盲等 500ms"与 `net_close_locked` 500ms 排空循环改走驱动 `sock_reset` 原语(`net_sock_reset_sync`,GRACEFUL 100ms 底),`net_close_locked` 不再读清全局中断寄存器(F-NET-7:全局寄存器唯一读者 = `net_poll`);新增 **`net_drop_conn(sockfd)`**(§10.1,E-02 收口:拆会话**保监听**、幂等、EOF 路径不得调;新消息类型 `NET_MSG_DROP_CONN`;timeSync 30s 预算耗尽路径接入;模拟器实现为关数据连接);`net_poll` 每轮对在用 Socket 步进 `ch395f_sock_reset_poll`(同步包装 600ms 放弃后复位机不搁浅);`net_sock_state_t` 注释声明为"本层会话投影"(芯片事实源 = 驱动缓存,`net_types.h`);台架回归全绿(最终构建 Code=211348,E-02 双 PASS,见 `docs/网络/网络自愈架构设计.md` §5.2 E-02) |
+| 2026-10-13 | **自愈收敛阶段 1.5 recv 语义主流化**(规格现沉淀 `docs/网络/网络自愈架构设计.md` §4.5):`net_recv()` 默认**无限阻塞**(删 `NET_RECV_TIMEOUT_MS` 30s 默认预算,唤醒源=数据/EOF/close/网络重建 CONNRESET);新增 `net_recv_nb()`(`NET_MSG_DONTWAIT` 路径转正)与 `net_setblocking(fd, on)`(模式挂 socket 控制块属性,`net_sock_t.blocking`,默认阻塞);`net_recv_to()` 保留为**显式预算**便捷入口(`timeout_ms=0` 语义从"默认 30s"改为"无限"故仍拒 INVAL);内部 recv 看门狗降级为 **tripwire**(挂起超 60s 只 `[ERR]` 留痕一次,不再"到点 +1s 强制了结"伪造 TIMEDOUT);⚠ 新增**文档契约**:IWDG `required_mask` 内任务禁止无限阻塞 recv(必须 `net_select` 超时或 `net_recv_to` 显式预算);`ui_net_sink` / `iec104_serv` 迁移 `net_recv_nb`,lftpd / timeSync 维持显式预算;模拟器 `net_socket_sim.c` 镜像三 API(blocking 属性包装层模拟) |
+| 2026-10-13 | **自愈收敛阶段1 API 面收敛**(审计过程已归档 `docs/网络/网络自愈架构设计.md` §9,现状见其 §4):删除手动自愈 API `net_relisten()` / `net_resync_listen()`(生产零调用;后者语义并入对账**⑤**——本层 ESTABLISHED 而芯片 0x05/0x01 静默回收 → RESYNC 只修本层,§3.6 对账由两类扩为三类①②⑤;前者与 pump/D10 重叠且对健康监听器是 footgun)与 `net_can_accept()`(只认 ESTABLISHED 的旧门控挡死丢 CONNECT 探针,监听门控统一 `net_listener_armed()`,§10.1 门控节改写);`NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 消息类型删除;timeSync 会话收尾不再 `net_close`(同 fd 续 accept,监听跨会话常驻);lftpd 应用层阻塞重试退役(就绪屏障已消除 TRAP-NET-09 启动形态) |
| 2026-10-12 | **N-32 监听生命周期模块化 + 单一所有者**:监听生命周期抽为 `net_listener.c`(§2.2 文件表新增该行,真机专属);主循环自愈 tick 由 3 个增至 **4 个**(新增 `net_listener_pump`,100ms,**唯一重建入口**);`net_reconcile_tick` 对账收窄为 ①②,③④ 移交 pump;`handle_disconnect_event` / `handle_timeout_event` 改为 `net_listener_settle(GRACEFUL 100ms / QUARANTINE 400ms)` **只置事实**、不再自行重建;§3.3 / §3.6(四层自愈 → **五层**)/ §12.10 同步。权威设计见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 |
| 2026-10-10 | 新增 **`net_can_accept()` / `net_can_read()` 就绪门控 API**(§10.1):纯 RAM 读控制块、**不走消息队列、不等 netTask 回执**,供实时性敏感任务在发 RPC 前判断"有没有活"(动因 = `ui_net_sink_poll()` 每 10 ms 一次 `net_accept` 把 uiTask 拖成最坏 ≈50 ms;原拟登记 N-19 / C7,当日结案不入登记簿;设计与实测见 `docs/子系统设计/看门狗设计说明.md` §11(原分析文档已并入该节));§3.5 补"netTask 运行要求"例外条;⚠ 记录两条使用约束——**不可作为唯一判据**(`readable` 只由 RECV_OK 中断置位,须保留低频兜底 recv)与**模拟器后端恒返 1**(`net_socket_sim.c`,Winsock 无 RPC 成本) |
| 2026-09-23 | 同步 L3 pending 化、D10 非阻塞重监听、TRAP-NET-17 对账、TRAP-NET-18 排渣隔离后的现状:新增 `net_recv_to` / `net_sock_state` / `net_resync_listen` / `net_relisten`(§10.1)及 `net_getsockname`;§3 新增 3.6(对账与自愈四机制)、3.3/3.5 改写(recv 挂起模型、队列语义、netTask 运行要求);§4/§5 示例更新(监听由就绪屏障后服务任务建立、`net_sock_state` 轮询);§10.2 改述 `net_process_messages()` 每轮节奏(重监听重试 → 挂起 recv 完成 → 200ms 对账 → 队列),补 `timeout_ms`/新消息类型说明;新增 §11.2 分片阻塞读(`net_recv_to`);§12.1/12.3/12.4/12.9 更新,新增 §12.10(自愈行为数据与排障判据,含 TC-NET-604 结案) |
diff --git a/docs/网络/BSD_Socket_API_函数调用关系.md b/docs/网络/BSD_Socket_API_函数调用关系.md
index 505515ce..9521adf6 100644
--- a/docs/网络/BSD_Socket_API_函数调用关系.md
+++ b/docs/网络/BSD_Socket_API_函数调用关系.md
@@ -14,7 +14,7 @@
## 1. 一句话架构
-**一切芯片(CH395F)操作收敛到唯一执行者 netTask 串行完成**;其他任务通过"入队消息 + 任务通知"表达请求,结果只经**任务通知值**回传;阻塞等待**不独占 netTask**(挂起登记 + 主循环节拍完成);监听器**常驻**(`auto_relisten` + D10 非阻塞重试 + **TRAP-NET-17 周期对账 + 监听重建合一 `net_reconcile_tick`(唯一重建入口=形态③④,100ms)** + TRAP-NET-18 排渣隔离,四层自愈;N34 阶段 3 起原 N-32 监听器 pump 已并入对账)。
+**一切芯片(CH395F)操作收敛到唯一执行者 netTask 串行完成**;其他任务通过"入队消息 + 任务通知"表达请求,结果只经**任务通知值**回传;阻塞等待**不独占 netTask**(挂起登记 + 主循环节拍完成);监听器**常驻**(`auto_relisten` + D10 非阻塞重试 + **TRAP-NET-17 周期对账 + 监听重建合一 `net_reconcile_tick`(唯一重建入口=形态③④,100ms)** + TRAP-NET-18 排渣隔离,四层自愈;自愈收敛阶段 3 起原 N-32 监听器 pump 已并入对账)。
---
@@ -49,7 +49,7 @@ flowchart TB
NP["netPoll
INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 状态缓存全量刷新 + sock_reset 步进 → 每轮一批"]
T1["① netListenerRetryTick
D10 重监听重试:30ms / 2s 时限"]
T2["② netPendingTick
L3 挂起 recv:①数据 ②到点(仅显式预算) ③断连 ④tripwire 60s 只留痕"]
- T3["③ netReconcileTick
TRAP-NET-17 对账 + 监听重建合一(N34 阶段 3):100ms / 分级去抖 / 冷却
读驱动状态缓存(①②③④⑤ + U11)"]
+ T3["③ netReconcileTick
TRAP-NET-17 对账 + 监听重建合一(自愈收敛阶段 3):100ms / 分级去抖 / 冷却
读驱动状态缓存(①②③④⑤ + U11)"]
DISP["队列分派 switch
SEND · RECV · CLOSE
CONNECT · LISTEN · ACCEPT · DROP_CONN"]
subgraph LK["_locked / 内部分派目标(仅 netTask 上下文)"]
LL["netListenLocked
初次监听(open 重试 3× + sock_reset 排渣)"]
@@ -75,8 +75,8 @@ flowchart TB
T1 --> RLS
T3 -->|"① 已断"| EV
T3 -->|"② 补 accept"| EV
- T3 -->|"⑤ 静默回收:RESYNC 只修本层 + 取消挂起(不发芯片命令,N34 阶段1 吸收原 net_resync_listen 语义)"| RSY["reconcile⑤ RESYNC 动作"]
- T3 -->|"③④ 监听失位 / 孤儿监听即重建(原 pump,N34 阶段 3)"| RLS
+ T3 -->|"⑤ 静默回收:RESYNC 只修本层 + 取消挂起(不发芯片命令,自愈收敛阶段1 吸收原 net_resync_listen 语义)"| RSY["reconcile⑤ RESYNC 动作"]
+ T3 -->|"③④ 监听失位 / 孤儿监听即重建(原 pump,自愈收敛阶段 3)"| RLS
DC -->|"拆会话后即时重监听"| RLS
NP --> EV
EV --> FE
@@ -97,7 +97,7 @@ flowchart TB
1. 应用侧只有两种"等":① 入队后 `xTaskNotifyWait` 等回执(同步语义);② 挂起 recv 的等待(同一通道,只是"完成"由 `netPendingTick` 判定,不由 netTask 立即处理)。
2. `net_process_messages()` 开头的 **3 个 tick 是网络层自愈主干**:没有任何应用消息时,重试 / 挂起完成 / 状态对账 + 监听重建照常运转——"断连后即使没人发消息,监听器也能自己活过来"靠的就是它们。
-3. 事件处理器**只由 `net_poll` 驱动**,例外是 `net_reconcile_tick`(分级去抖 + 1s 冷却)可再入 `handle_disconnect_event` / `handle_connect_event`——对账是丢中断的自愈通道(TRAP-NET-17)。**注意职责边界(N-32 P2,N34 阶段 3 合并后)**:对账与事件处理器**只置事实**(置 CLOSED / 登记沉降),**重建监听的动作统一由 `net_reconcile_tick` 形态③④ 做**(本层唯一重建入口)——"谁先看到 DISCONNECT 就谁重建"的竞争在结构上消失。
+3. 事件处理器**只由 `net_poll` 驱动**,例外是 `net_reconcile_tick`(分级去抖 + 1s 冷却)可再入 `handle_disconnect_event` / `handle_connect_event`——对账是事件未收到的自愈通道(TRAP-NET-17)。**注意职责边界(N-32 P2,自愈收敛阶段 3 合并后)**:对账与事件处理器**只置事实**(置 CLOSED / 登记沉降),**重建监听的动作统一由 `net_reconcile_tick` 形态③④ 做**(本层唯一重建入口)——"谁先看到 DISCONNECT 就谁重建"的竞争在结构上消失。
4. 回调 `event_cb` 在 **netTask 上下文**执行:回调内不得阻塞、不得发网络操作。
5. **模块归属**:`T1`(D10 重试)/ `RLS` 的 `net_listener_try` / `ST`(settle)在 `net_listener.c`(真机专属、模拟器不编译);其余 tick(含合并后的 `T3` reconciler)与 `_locked` 在 `net_socket.c`。
@@ -117,8 +117,8 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
-| `net_poll()` | 事件泵:GPIO 轮询 INT#(低=有待处理)→ `GET_GLOB_INT_STATUS_ALL`(读=清)→ 按位图逐 Socket 读 SINT → 分派 5 类事件;**每轮末尾**对在用 Socket 全量刷新**驱动状态缓存** + 步进 `ch395f_sock_reset_poll`(N34 阶段 3) | `HAL_GPIO_ReadPin` → `ch395f_get_glob_int_status_all` → `ch395f_get_sock_int_status` ×n → `handle_connect/disconnect/recv/send/timeout_event` → `ch395f_get_sock_state` ×n(刷新缓存)+ `ch395f_sock_reset_poll` ×n | **RECV 中断是电平触发**:每次调用**只处理一批**(do-while(0)),处理完立即返回,否则主循环(echo/应用逻辑)饿死(TRAP-NET-08);SENBUF_FREE 只置 `send_ready` 不记事件;状态缓存**唯一刷新点**在此(reconciler 只读缓存,E2 实测单次回读 18.1µs);`sock_reset_poll` 步进使同步包装 600ms 放弃后的复位机不搁浅(IDLE 槽纯 RAM 判零 SPI) |
-| `net_process_messages()` | 队列入口 + 自愈 tick 宿主:每轮**先**跑 3 个非阻塞 tick,再排空队列逐条分派,结果 `xTaskNotify` 回传 | `net_listener_retry_tick` → `net_pending_tick` → `net_reconcile_tick`(对账+重建合一)→ `osMessageQueueGet`(0 超时) ×n → switch 分派 → `xTaskNotify` | ① tick 放在队列**之前**:断连后即使没有任何应用消息,监听器也能自主恢复、挂起 recv 也能完成;② RECV 若返回 `NET_PENDING_REGISTERED`(哨兵 0x70000000)则**不发即时回执**(`do_notify=0`),完成时才通知;③ 失败结果把 `s_net_errno`(具体负错误码)压入通知值——调用方恢复 errno 后对外统一 -1(BSD 语义);④ 通知值**整个 32 位**都是结果(不压位域标签,否则 32768 这类大字节数会溢出为负);⑤ **对账①置 CLOSED 后形态③④同轮即可重建**(N34 阶段 3 合并,原"pump 置于对账之后"的顺序依赖在单 tick 内自然成立) |
+| `net_poll()` | 事件泵:GPIO 轮询 INT#(低=有待处理)→ `GET_GLOB_INT_STATUS_ALL`(读=清)→ 按位图逐 Socket 读 SINT → 分派 5 类事件;**每轮末尾**对在用 Socket 全量刷新**驱动状态缓存** + 步进 `ch395f_sock_reset_poll`(自愈收敛阶段 3) | `HAL_GPIO_ReadPin` → `ch395f_get_glob_int_status_all` → `ch395f_get_sock_int_status` ×n → `handle_connect/disconnect/recv/send/timeout_event` → `ch395f_get_sock_state` ×n(刷新缓存)+ `ch395f_sock_reset_poll` ×n | **RECV 中断是电平触发**:每次调用**只处理一批**(do-while(0)),处理完立即返回,否则主循环(echo/应用逻辑)饿死(TRAP-NET-08);SENBUF_FREE 只置 `send_ready` 不记事件;状态缓存**唯一刷新点**在此(reconciler 只读缓存,E2 实测单次回读 18.1µs);`sock_reset_poll` 步进使同步包装 600ms 放弃后的复位机不搁浅(IDLE 槽纯 RAM 判零 SPI) |
+| `net_process_messages()` | 队列入口 + 自愈 tick 宿主:每轮**先**跑 3 个非阻塞 tick,再排空队列逐条分派,结果 `xTaskNotify` 回传 | `net_listener_retry_tick` → `net_pending_tick` → `net_reconcile_tick`(对账+重建合一)→ `osMessageQueueGet`(0 超时) ×n → switch 分派 → `xTaskNotify` | ① tick 放在队列**之前**:断连后即使没有任何应用消息,监听器也能自主恢复、挂起 recv 也能完成;② RECV 若返回 `NET_PENDING_REGISTERED`(哨兵 0x70000000)则**不发即时回执**(`do_notify=0`),完成时才通知;③ 失败结果把 `s_net_errno`(具体负错误码)压入通知值——调用方恢复 errno 后对外统一 -1(BSD 语义);④ 通知值**整个 32 位**都是结果(不压位域标签,否则 32768 这类大字节数会溢出为负);⑤ **对账①置 CLOSED 后形态③④同轮即可重建**(自愈收敛阶段 3 合并,原"pump 置于对账之后"的顺序依赖在单 tick 内自然成立) |
### 4.3 经队列的公共 API 及其 netTask 侧实现
@@ -126,17 +126,17 @@ flowchart TB
| 公共 API | 消息 | netTask 侧实现 | 关键芯片命令 | 返回语义 | 设计意图 / 注意 |
|---|---|---|---|---|---|
-| `net_listen` | `NET_MSG_LISTEN` | `net_listen_locked` | `set_proto_type` + `set_sour_port` + `open_socket`(**open 重试 3×,失败走 `net_sock_reset_sync` 确定性排渣**,N34 阶段 3)+ `tcp_listen` + 清**本 Socket** 中断状态 | 0 / -1 | **初次**监听允许有界重试(启动期无竞争);成功后置 `auto_relisten=1`——监听常驻的源头;启动前只清本 Socket SINT(B3/F-NET-7:全局寄存器唯一读者 = `net_poll`,全量读清会焊死其它 Socket 事件位);open 失败重试原"close+盲等 500ms"已换 sock_reset(GRACEFUL 100ms 底,E4 定案复位后 open 必成) |
-| `net_accept` | `NET_MSG_ACCEPT` | `net_accept_locked` | 无(先 `net_poll` 泵一次 + 状态判定) | 监听 fd 自身(单连接)/ -1(WOULDBLOCK) | 单连接:监听 Socket 在 CONNECT 后**自身即数据通道**,返回的就是它;CLOSED + auto_relisten 时触发**非阻塞** `net_listener_try`;**accept 路径不做自愈**——单次状态读会误判"刚 listen 未生效"致重建风暴(2026-09-23 实测),统一交给 `net_reconcile_tick` 形态③④(N34 阶段 3 合并后的单一重建入口) |
+| `net_listen` | `NET_MSG_LISTEN` | `net_listen_locked` | `set_proto_type` + `set_sour_port` + `open_socket`(**open 重试 3×,失败走 `net_sock_reset_sync` 确定性排渣**,自愈收敛阶段 3)+ `tcp_listen` + 清**本 Socket** 中断状态 | 0 / -1 | **初次**监听允许有界重试(启动期无竞争);成功后置 `auto_relisten=1`——监听常驻的源头;启动前只清本 Socket SINT(B3/F-NET-7:全局寄存器唯一读者 = `net_poll`,全量读清会焊死其它 Socket 事件位);open 失败重试原"close+盲等 500ms"已换 sock_reset(GRACEFUL 100ms 底,E4 定案复位后 open 必成) |
+| `net_accept` | `NET_MSG_ACCEPT` | `net_accept_locked` | 无(先 `net_poll` 泵一次 + 状态判定) | 监听 fd 自身(单连接)/ -1(WOULDBLOCK) | 单连接:监听 Socket 在 CONNECT 后**自身即数据通道**,返回的就是它;CLOSED + auto_relisten 时触发**非阻塞** `net_listener_try`;**accept 路径不做自愈**——单次状态读会误判"刚 listen 未生效"致重建风暴(2026-09-23 实测),统一交给 `net_reconcile_tick` 形态③④(自愈收敛阶段 3 合并后的单一重建入口) |
| `net_connect` | `NET_MSG_CONNECT` | `net_connect_locked` | `set_proto_type` + `open_socket`(open 重试 3× + `net_sock_reset_sync` 排渣)+ 清残留中断(只清本 Socket SINT,`ch395f_sock_events`)+ `set_sour_port` / `set_des_ip`(大端数组)/ `set_des_port` + `tcp_connect` | 0 = 请求已发出(**异步**)/ -1 | open **先于**寄存器写入(open 会复位寄存器);open 后清残留 DISCONNECT,防新连接秒断(TRAP-NET-12);目的 IP 字节序写反 = CONNECT TIMEOUT(TRAP-NET-14);返回 0 ≠ 已建立——调用方以 `net_sock_state` 轮询等 ESTABLISHED |
| `net_send` | `NET_MSG_SEND` | `net_send_locked` | `ch395f_write_send_buf`(分块)+ 等 SENBUF_FREE(`net_poll` 轮询) | 实际字节数 / -1 | 分块 ≤ `NET_SEND_CHUNK_MAX` = 1KB(**硬件发送 FIFO 硬上限**,TRAP-NET-16:写穿 FIFO = 数据错位);`send_ready` 门控用 `net_poll` 轮询而非 osDelay;短写**必须**告警——静默截断会让对端永久等待剩余字节(2026-09-21 实证:UI 推流应答截断 → 客户端失联) |
-| `net_recv` | `NET_MSG_RECV`(timeout=0→**无限等待**,N34 阶段 1.5 删 30s 默认) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / CONNRESET;阻塞形态无 TIMEDOUT 一档) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞(`blocking` 属性=1 且未 DONTWAIT)→ **登记挂起后立即返回**(L3,netTask 零占用),唤醒源=数据/EOF/close/网络重建;DONTWAIT / `blocking=0` + CLOSED/session_dead → EOF + 触发重监听;⚠ IWDG 受监控任务禁用无限形态(`net_socket.h` 文档契约) |
-| `net_recv_nb` | `NET_MSG_RECV`(flags=DONTWAIT) | `net_recv_locked` | 同上 | >0 / 0 / -1(WOULDBLOCK 等) | 原 `NET_MSG_DONTWAIT` 路径转正的具名入口(N34 阶段 1.5);不改变 `blocking` 属性;受 IWDG 监控 / 有周期职责任务的推荐默认(ui_net_sink / iec104_serv 已迁) |
-| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套 + **显式预算**入口(N34 阶段 1.5,等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) |
-| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣(N34 阶段 3) | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,N-30) |
-| `net_drop_conn` | `NET_MSG_DROP_CONN`(N34 阶段 3 新增) | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_try` | 0(含幂等)/ -1 | **拆会话保监听**(E-02 收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 即时重监听、失败交 D10 + reconciler 形态④ |
-| ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **N34 阶段1 已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
-| ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **N34 阶段1 已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 |
+| `net_recv` | `NET_MSG_RECV`(timeout=0→**无限等待**,自愈收敛阶段 1.5 删 30s 默认) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / CONNRESET;阻塞形态无 TIMEDOUT 一档) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞(`blocking` 属性=1 且未 DONTWAIT)→ **登记挂起后立即返回**(L3,netTask 零占用),唤醒源=数据/EOF/close/网络重建;DONTWAIT / `blocking=0` + CLOSED/session_dead → EOF + 触发重监听;⚠ IWDG 受监控任务禁用无限形态(`net_socket.h` 文档契约) |
+| `net_recv_nb` | `NET_MSG_RECV`(flags=DONTWAIT) | `net_recv_locked` | 同上 | >0 / 0 / -1(WOULDBLOCK 等) | 原 `NET_MSG_DONTWAIT` 路径转正的具名入口(自愈收敛阶段 1.5);不改变 `blocking` 属性;受 IWDG 监控 / 有周期职责任务的推荐默认(ui_net_sink / iec104_serv 已迁) |
+| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套 + **显式预算**入口(自愈收敛阶段 1.5,等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) |
+| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣(自愈收敛阶段 3) | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,N-30) |
+| `net_drop_conn` | `NET_MSG_DROP_CONN`(自愈收敛阶段 3 新增) | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_try` | 0(含幂等)/ -1 | **拆会话保监听**(E-02 收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 即时重监听、失败交 D10 + reconciler 形态④ |
+| ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **自愈收敛阶段1 已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
+| ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **自愈收敛阶段1 已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 |
### 4.4 直调 API(不经队列)
@@ -144,8 +144,8 @@ flowchart TB
|---|---|---|---|
| `net_socket(domain,type)` | 分配空闲控制块(`alloc_socket` 线性扫描 `in_use=0`)→ CREATED | `alloc_socket` + `net_listener_clear` + `net_pending_clear` | 新 fd 不得继承上一次的待重试/挂起状态(复用描述符的经典坑);domain 仅 `NET_AF_INET` |
| `net_bind(sockfd,addr,addrlen)` | TCP:仅记录本地端口(port=0 → 动态端口 49152 起递增)→ BOUND,**无芯片命令**;UDP:完整开 Socket → UDP_OPEN | UDP 路径:`set_proto_type(UDP)` + `set_sour_port` + `set_des_ip(0xFFFFFFFF)` + `set_des_port(0)` + `open_socket`(Socket 4~7 兜底重设缓冲) | TCP 的 bind 只是"记账",真正的芯片动作在 listen/connect;UDP 直碰 SPI(§2 三例外之一);动态端口回绕 65535→49152 |
-| `net_sendto` / `net_recvfrom` | UDP 收发(直碰 SPI,不经队列) | `set_des_ip` / `set_des_port` + `write_send_buf`(分块 1KB + `net_poll` 门控);`net_poll` + `get_recv_len` + `read_recv_buf` | 同 §2 上下文约束;recv 时 UDP Server 模式前 8 字节是固定头(源 IP + 端口 + 长度),先剥头填 `src_addr` 再读数据;recvfrom 阻塞语义与 net_recv 同构(N34 阶段 1.5:blocking 属性 / DONTWAIT,无默认预算;轮询自旋实现,生产零调用) |
-| `net_setblocking(sockfd,on)` | 设置阻塞**模式属性**(N34 阶段 1.5,直写控制块,不经队列) | 写 `s_net_socks[fd].blocking` | 对齐 BSD `O_NONBLOCK` / Python `setblocking`:"模式归使用者、超时归调用方";单字节原子写(先例 `net_set_event_cb`),最坏一次在途 recv 用旧模式;`net_socket()` 默认置 1(close 的 memset 清零后新 fd 必须显式重置) |
+| `net_sendto` / `net_recvfrom` | UDP 收发(直碰 SPI,不经队列) | `set_des_ip` / `set_des_port` + `write_send_buf`(分块 1KB + `net_poll` 门控);`net_poll` + `get_recv_len` + `read_recv_buf` | 同 §2 上下文约束;recv 时 UDP Server 模式前 8 字节是固定头(源 IP + 端口 + 长度),先剥头填 `src_addr` 再读数据;recvfrom 阻塞语义与 net_recv 同构(自愈收敛阶段 1.5:blocking 属性 / DONTWAIT,无默认预算;轮询自旋实现,生产零调用) |
+| `net_setblocking(sockfd,on)` | 设置阻塞**模式属性**(自愈收敛阶段 1.5,直写控制块,不经队列) | 写 `s_net_socks[fd].blocking` | 对齐 BSD `O_NONBLOCK` / Python `setblocking`:"模式归使用者、超时归调用方";单字节原子写(先例 `net_set_event_cb`),最坏一次在途 recv 用旧模式;`net_socket()` 默认置 1(close 的 memset 清零后新 fd 必须显式重置) |
| `net_wait_result(kind,sockfd)` | 调用侧两段式等待:**零等待拾取** → 阻塞等回执 | `xTaskNotifyWait` ×2 | ① 零等待段兜住"入队→等待入口"间隙落地的通知(2026-09-22 台架实证丢通知);② **清除掩码必须全 1**(传 0 = 不清 → 上次结果残留被重读,run3~11 挂死/错位的真根因);③ 必须判 **pdTRUE** 而非 `v!=0`(合法结果 0 = EOF 会被误判"无通知"→ 永久挂死);④ 32 位整体作结果(32768 大字节数不截断);⑤ 本路径**禁止**加逐调用日志(曾占日志 98.6%、冲掉 [LAT] 证据行,run14 实证) |
| `net_sock_state(sockfd)` | 跨任务只读状态快照(单字节读,无锁) | 无 | 描述符无效/未占用 → -1;值可能滞后 netTask 最新状态——用于**轮询**(客户端等 ESTABLISHED),不替代 accept/connect |
| `net_get_sock(sockfd)` | 返回控制块指针 | 无 | netTask 上下文内用;跨任务只可读单字节 `state`(即 `net_sock_state`),不得读结构体其他成员 |
@@ -161,7 +161,7 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `net_pending_register` | 登记挂起(`net_recv_locked` 的"无数据 + 阻塞"路径) | 写 `s_net_pending[fd]` | 每 fd 至多一个:**覆盖会丢失前调用方通知 = 永久挂死**;执行任务自身(netTask)发起的等待直接拒绝(自等自通知 = 死锁,netTask 内须用 DONTWAIT) |
-| `net_pending_tick` | 完成检查(`net_process_messages` 每轮调用,非阻塞) | ① `get_recv_len` + `read_recv_buf` ② 到点(仅显式预算) ③ `get_recv_len` 排空 + `net_listener_try` ④ `net_pending_complete` | 按 **①数据 ②到点(仅 `net_recv_to` 显式预算;无限登记无 deadline)③断连** 顺序检查(逐项对齐旧自旋语义);CONNECTING/LISTENING 等状态**保持等待**(CONNECT→ESTABLISHED 是合法迁移,不做状态推断取消,防误杀正在建链的等待);**tripwire**(N34 阶段 1.5,原看门狗降级):挂起超 60s = 完成逻辑缺位 → 只 `[ERR]` 留痕一次,**绝不强制了结、绝不伪造 TIMEDOUT**——③ 与显式 cancel 是无限 recv 的全部唤醒责任方 |
+| `net_pending_tick` | 完成检查(`net_process_messages` 每轮调用,非阻塞) | ① `get_recv_len` + `read_recv_buf` ② 到点(仅显式预算) ③ `get_recv_len` 排空 + `net_listener_try` ④ `net_pending_complete` | 按 **①数据 ②到点(仅 `net_recv_to` 显式预算;无限登记无 deadline)③断连** 顺序检查(逐项对齐旧自旋语义);CONNECTING/LISTENING 等状态**保持等待**(CONNECT→ESTABLISHED 是合法迁移,不做状态推断取消,防误杀正在建链的等待);**tripwire**(自愈收敛阶段 1.5,原看门狗降级):挂起超 60s = 完成逻辑缺位 → 只 `[ERR]` 留痕一次,**绝不强制了结、绝不伪造 TIMEDOUT**——③ 与显式 cancel 是无限 recv 的全部唤醒责任方 |
| `net_pending_complete` | 所有完成/取消路径的公共出口 | `xTaskNotify(caller, result)` + 清 active | 通知值 = 结果原值(>0 字节 / 0 EOF / 负错误码,与消息分派同一回传通道);失败结果同步 `s_net_errno`(netTask 上下文一致性);无在位挂起时静默返回(防重复通知) |
| `net_pending_settle_close` | close 前**了结**本 fd 挂起 | `get_recv_len` + `read_recv_buf` + `net_pending_complete` | 关闭不得丢已到达数据(芯片缓冲 close 前可读):有数据 → 按字节数通知;无数据 → 取消(CONNRESET);先于 `ch395f_close_socket` 执行 |
| `net_pending_cancel` / `cancel_all` | 显式取消(**必通知**) | `net_pending_complete` | 取消路径:陈旧 resync(单 fd)/ 网络重建(全部,`net_init` 开头,早于控制块清零)——"取消不通知 = 调用方永久挂死" |
@@ -183,9 +183,9 @@ flowchart TB
| `net_listener_clear` | 取消该 fd 的待重试/沉降 | 清 `wait/next/dead` + `settle_dead/settle_start` + `quarantine` | 触发点:`net_socket`(新 fd 不继承)/ `net_close_locked`(关闭即取消);沉降/隔离状态必须一并清,否则残留标记会污染下一次复监听 |
| `net_drain_chip_rx` | 排空芯片 RX 残留(256B 分块,≤16 次 = 4KB 上限) | `get_recv_len` + `read_recv_buf` | TRAP-NET-18:中止/异常断开时芯片 RX 缓冲残留上一条连接的数据(实测 ~2048B),不排空会被下一条连接当自己的数据读走(静默截断);分块读避免新增静态缓冲(内存预算紧张) |
-### 4.7 监听重建(原 N-32 监听器 pump;N34 阶段 3 已并入 `net_reconcile_tick` 形态③④/U11)
+### 4.7 监听重建(原 N-32 监听器 pump;自愈收敛阶段 3 已并入 `net_reconcile_tick` 形态③④/U11)
-> **N34 阶段 3(2026-10-13)**:`net_listener_pump()` 已**删除**——其全部三种情形作为**形态③④ + U11** 并入 `net_reconcile_tick`(§4.8,单一 100ms tick,判据读驱动状态缓存)。本节保留"单一重建所有者"的设计口径与情形表;实现位置与参数以 §4.8 为准。
+> **自愈收敛阶段 3(2026-10-13)**:`net_listener_pump()` 已**删除**——其全部三种情形作为**形态③④ + U11** 并入 `net_reconcile_tick`(§4.8,单一 100ms tick,判据读驱动状态缓存)。本节保留"单一重建所有者"的设计口径与情形表;实现位置与参数以 §4.8 为准。
**设计意图(设计文档 §4 不变量 II)**:其它所有位置**只置事实**(会话结束 / 状态迁移 / 事件派发),**不做重建动作**——于是"谁先看到 DISCONNECT 就谁重建"这类**多所有者竞争在结构上消失**。`net_reconcile_tick` 形态③④是**本层唯一**的周期性重建决策者。
@@ -197,17 +197,17 @@ flowchart TB
| ④ | 本层 CLOSED(会话已结束)而芯片**不在** LISTEN | 重建(`net_listener_try`) | 原对账④ / 原 pump② |
| U11 | 本层 CLOSED 而芯片**正在** LISTEN | **只做状态重同步**(CLOSED→LISTENING),**不发任何芯片命令** | U11 缺口 |
-情形 U11 的理由:芯片监听器是**健康**的,重下发 open+listen 会因 `USE` 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);只把本层状态收回 LISTENING 即可(与对账⑤ RESYNC 同一纪律——原 `net_resync_listen`,N34 阶段1 已退役)。
+情形 U11 的理由:芯片监听器是**健康**的,重下发 open+listen 会因 `USE` 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故);只把本层状态收回 LISTENING 即可(与对账⑤ RESYNC 同一纪律——原 `net_resync_listen`,自愈收敛阶段1 已退役)。
**去抖 + 冷却**(③④ 沿用原实测结论,取值不变):同一失配需**连续 2 轮**(`NET_RECON_STABLE_RELISTEN`,≈200ms)才动手;同一 Socket 动手后**冷却 1s**(防抖成风暴,2026-09-23 实证);U11 被动重同步**不去抖、不占冷却**(零风险)。**tick 周期 100ms**(原 pump 周期;合并后对账也从 200ms 提到 100ms):比 200ms 快、又不至于像 50ms 那样频繁 close/open 与消费者收发撞车(2026-10-12 台架实测 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms)。
> `session_dead` **不在此清零**(与 `net_relisten_locked` 同一契约:重建监听 ≠ 应用已确认会话结束;清零只发生在 `handle_connect_event`)。
-### 4.8 TRAP-NET-17 周期对账 + 监听重建(合一 `net_reconcile_tick`,丢中断自愈,`net_socket.c`)
+### 4.8 TRAP-NET-17 周期对账 + 监听重建(合一 `net_reconcile_tick`,事件未收到自愈,`net_socket.c`)
-`net_reconcile_tick()`(`net_process_messages` 每轮调用;周期 **100ms**,`NET_RECONCILE_PERIOD_MS`;N34 阶段 3 起含原 pump 形态③④/U11):
+`net_reconcile_tick()`(`net_process_messages` 每轮调用;周期 **100ms**,`NET_RECONCILE_PERIOD_MS`;自愈收敛阶段 3 起含原 pump 形态③④/U11):
-**问题**:芯片在快速复连场景会**丢 CONNECT/DISCONNECT 中断**(或静默回收 Socket 不发中断);丢事件后纯事件驱动无法自愈——典型:对端连接一直占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒,而应用因未收到 DISCONNECT 仍以为有客户端(永不调 accept)。
+**问题**:芯片在快速复连场景会**收不到 CONNECT/DISCONNECT 事件**(读清消费竞态——E14 复核:非芯片侧丢失;或芯片静默回收 Socket 不发中断);事件未收到时纯事件驱动无法自愈——典型:对端连接一直占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒,而应用因未收到 DISCONNECT 仍以为有客户端(永不调 accept)。
**机制**:对每个在用 Socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs;**本 tick 不再自发状态 SPI**,唯一按需 SPI = 形态② 命中时回读对端端口做幽灵甄别),判定**五类**失同步 + 一类被动重同步并修正:
@@ -217,11 +217,11 @@ flowchart TB
| ② | 本层 LISTENING 而芯片已完成握手(tcp = ESTABLISHED) | 补做 standalone accept 簿记 | `handle_connect_event` |
| ③ | 本层 LISTENING(监听型 fd)而芯片监听失位 | 重建 | `net_listener_try`(原 pump①) |
| ④ | 本层 CLOSED(监听型 fd)而芯片不在 LISTEN | 重建 | `net_listener_try`(原 pump②) |
-| ⑤ | 本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01,无 DISCONNECT 中断) | RESYNC 只修本层、不发芯片命令 | 原 `net_resync_listen` 语义(N34 阶段1 并入) |
+| ⑤ | 本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01,无 DISCONNECT 中断) | RESYNC 只修本层、不发芯片命令 | 原 `net_resync_listen` 语义(自愈收敛阶段1 并入) |
| U11 | 本层 CLOSED(监听型 fd)而芯片**正在** LISTEN | 只重同步 CLOSED→LISTENING,不发芯片命令 | 原 pump③ |
> ⚠ **N-32 P2(2026-10-12)**:原③(本层 LISTENING 丢监听)与④(孤儿监听)曾**整体移交 `net_listener_pump`**——本函数内**不再有任何重建动作**(单一所有者),认领门槛相应收窄为 ESTABLISHED/LISTENING(原为驱动④而多认领"CLOSED + auto_relisten + 已绑端口")。
-> ⚠ **N34 阶段 3(2026-10-13)**:pump 已删除,③④/U11 **回到本 tick**(单一 100ms tick 覆盖①②③④⑤/U11);"单一所有者"不变量不变——重建决策仍只此一处,认领门槛按原 pump 口径以 `listener_fd` 门恢复。
+> ⚠ **自愈收敛阶段 3(2026-10-13)**:pump 已删除,③④/U11 **回到本 tick**(单一 100ms tick 覆盖①②③④⑤/U11);"单一所有者"不变量不变——重建决策仍只此一处,认领门槛按原 pump 口径以 `listener_fd` 门恢复。
**去抖 + 冷却(缺一不可)**:单次状态读可能落在"刚 open/listen 尚未生效"的瞬态——**2026-09-23 实测**:无去抖的 accept 路径自愈把监听反复重建直至 `relisten give up`(三服务口全死、89 行 ERROR)。故**分级去抖**(单位=轮,100ms/轮):②=1 轮(`NET_RECON_STABLE_ACCEPT`,幽灵甄别兜底假阳性);③④⑤=2 轮(`NET_RECON_STABLE_RELISTEN`);①=4 轮(`NET_RECON_STABLE_DEATH`,**勿改小**——F-NET-17 M3 可疑电平让位再入的 400ms 租约,E9a 实证);同一 Socket 动手后**冷却 1s**(`NET_RECONCILE_COOLDOWN_MS`;U11 被动重同步不去抖不占冷却)。开销:每 100ms 每在用 Socket 的状态回读已收拢在 `net_poll`(≈100µs/轮),本 tick 本身零额外 SPI。
@@ -230,7 +230,7 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `handle_connect_event` | LISTENING → standalone accept(回读对端地址 → ESTABLISHED + `send_ready=1` + 清残留 TIMEOUT + 回调);CONNECTING → 客户端连接成功(回读对端 + 使能 KeepAlive + 回调) | `ch395f_get_remot_ipp` + `get_sock_int_status` +(client)`set_keepalive_enable` + `fire_event` | GET_REMOT_IPP 为**小端**输出(与 set_des_ip 的大端相反)——解析别抄反;`session_dead` 的**唯一清零点** |
-| `handle_disconnect_event` | 状态机迁移 + 陈旧事件甄别 + **登记沉降事实**(不再自己重建) | `ch395f_get_socket_status`(事件路径**主动 SPI**,不用缓存——缓存刷新在 net_poll 末尾,事件路径上必然滞后)→(LISTENING:只回读留痕,监听失位交 **reconciler 形态③** 重建;仍在监听 → 判陈旧丢弃)/(ESTABLISHED:芯片仍 ESTABLISHED → 判陈旧丢弃;F-NET-17 M3 可疑门 → 置可疑电平让位对账①) | ① **陈旧 DISCONNECT 甄别**(D20):同一 fd"关闭→重监听→再连接"循环复用,上一条连接的 FIN/ACK 可能在 fd 已复用时才上报 → 不甄别则新连接秒断(实测:FTP 同会话第二个数据连接必 425);② 正常释放(FIN)→ **GRACEFUL 沉降 100ms**(只置事实,重建由 `net_reconcile_tick` 形态③④ 做,N-32 P1/P2 + N34 阶段 3);③ 置 CLOSED 后**不立即关芯片 Socket**——留给 `net_recv_locked` 排空剩余数据(FIN 可能先于数据到达) |
+| `handle_disconnect_event` | 状态机迁移 + 陈旧事件甄别 + **登记沉降事实**(不再自己重建) | `ch395f_get_socket_status`(事件路径**主动 SPI**,不用缓存——缓存刷新在 net_poll 末尾,事件路径上必然滞后)→(LISTENING:只回读留痕,监听失位交 **reconciler 形态③** 重建;仍在监听 → 判陈旧丢弃)/(ESTABLISHED:芯片仍 ESTABLISHED → 判陈旧丢弃;F-NET-17 M3 可疑门 → 置可疑电平让位对账①) | ① **陈旧 DISCONNECT 甄别**(D20):同一 fd"关闭→重监听→再连接"循环复用,上一条连接的 FIN/ACK 可能在 fd 已复用时才上报 → 不甄别则新连接秒断(实测:FTP 同会话第二个数据连接必 425);② 正常释放(FIN)→ **GRACEFUL 沉降 100ms**(只置事实,重建由 `net_reconcile_tick` 形态③④ 做,N-32 P1/P2 + 自愈收敛阶段 3);③ 置 CLOSED 后**不立即关芯片 Socket**——留给 `net_recv_locked` 排空剩余数据(FIN 可能先于数据到达) |
| `handle_recv_event` | 置 `readable` + 回调;**不读 recv_len** | `fire_event` | 不读长度是为了避免与 `net_recv_locked` 的 `ch395f_get_recv_len` 竞争(旧"分批读归零"机理,未证实);`readable` 供 `net_select` 无 SPI 访问判读 |
| `handle_send_event` | 发送缓冲已空:触发回调(`send_ready` 由 SENBUF_FREE 前置位) | `fire_event` | — |
| `handle_timeout_event` | CONNECTING → CREATED(连接超时);**ESTABLISHED → 登记隔离 + CLOSED**(TRAP-NET-18);LISTENING → 仅记日志 | **`net_listener_settle(fd, NET_LISTENER_SETTLE_QUARANTINE)`** + `fire_event` | TIMEOUT = 异常释放(如客户端中止上传):对端在途数据随后落进 RX 缓冲 → 必须隔离(400ms)后才许复监听;正常 FIN 释放走 disconnect 路径(GRACEFUL 100ms),**不走**隔离 |
@@ -326,7 +326,7 @@ sequenceDiagram
Note over N: 2s 超限时 → 清状态 + "relisten give up"(错误日志,应用可感知)
```
-### 5.4 丢中断自愈(TRAP-NET-17 对账 + 监听重建合一 reconciler,N34 阶段 3)
+### 5.4 事件未收到自愈(TRAP-NET-17 对账 + 监听重建合一 reconciler,自愈收敛阶段 3)
```mermaid
sequenceDiagram
@@ -334,7 +334,7 @@ sequenceDiagram
participant C as CH395F
participant A as appTask
- Note over C: 快速复连:芯片完成握手但 CONNECT 中断丢失
+ Note over C: 快速复连:芯片完成握手但 CONNECT 事件未收到
Note over N: 本层滞留 LISTENING → 应用以为无客户端(永不调 accept)
loop 每 100ms(net_poll 每轮刷新驱动状态缓存)
N->>N: net_reconcile_tick:读 ch395f_sock_state_cached(每个在用 socket)
@@ -369,7 +369,7 @@ sequenceDiagram
|---|---|---|
| 1 | **一切芯片访问收敛 netTask**(唯一执行者);跨任务调用走队列 | TRAP-NET-14:跨任务 SPI 竞争破坏芯片寄存器 → 连接必超时 |
| 2 | **结果只经任务通知值回传**;消息按值入队,调用方看不到 netTask 对消息的写入;勿给消息加"回传用"字段 | TRAP-NET-10:6 个 API 永远返回 -1;"加 msg.err 字段"的修法曾致 `ui_net_sink` 每次空轮询误判真错误 → 连上即断 |
-| 3 | **netTask 不为任何人自旋等待**:阻塞 recv = 挂起登记(L3);重开失败 = 重试登记(D10);初次 listen/connect 的 open 重试环(`net_sock_reset_sync` 有界等待,GRACEFUL 100ms 底 / 600ms 封顶)是唯一豁免(启动期无竞争,N34 阶段 3 起原 3×500ms 盲等已换确定性排渣) | E-02:一次空闲 30s recv 冻住全网 17.7s;旧重监听 osDelay(500) → 恢复 588ms、期间重连被拒 |
+| 3 | **netTask 不为任何人自旋等待**:阻塞 recv = 挂起登记(L3);重开失败 = 重试登记(D10);初次 listen/connect 的 open 重试环(`net_sock_reset_sync` 有界等待,GRACEFUL 100ms 底 / 600ms 封顶)是唯一豁免(启动期无竞争,自愈收敛阶段 3 起原 3×500ms 盲等已换确定性排渣) | E-02:一次空闲 30s recv 冻住全网 17.7s;旧重监听 osDelay(500) → 恢复 588ms、期间重连被拒 |
| 4 | **netTask 不向自身发消息 / 不自我挂起**(挂起登记对执行任务自身发起直接拒绝) | 自等自通知 = 死锁 |
| 5 | **基于状态读的重建动作必须去抖 + 冷却**:分级门槛 ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(100ms/轮)+ 冷却 1s | 2026-09-23:无去抖 → 监听反复重建直至 `relisten give up`,三服务口全死 |
| 6 | **异常释放(TIMEOUT)后复监听前必须隔离**:排空 RX 残渣 + 等 CLOSED/TCP_CLOSED;隔离上限 < 重试放弃时限 | TRAP-NET-18:~2KB 残渣被下条连接当自己的数据 → 首笔上传截断 2048B 并误报 226 成功;上限=2s 时隔离耗尽重试预算 → 监听被弃 |
@@ -377,7 +377,7 @@ sequenceDiagram
| 8 | **close 前先了结挂起 recv + 排空残留 DISCONNECT** | TRAP-NET-12:锁存的陈旧 DISCONNECT 秒断下一连接 |
| 9 | **两段式等待防丢失通知**:零等待拾取 + 阻塞;清除掩码全 1;以 pdTRUE 判定 | run3~11:丢失通知 → 挂死 / 结果错位;0 结果误判"无通知" → 永久挂死 |
| 10 | **网络重建先取消并通知全部挂起,再复位芯片** | 取消不通知 = 调用方永久挂死 |
-| 11 | **监听重建只有一个所有者**(`net_reconcile_tick` 形态③④,N34 阶段 3 合并后;原 `net_listener_pump` 已删);其它位置**只置事实**(置 CLOSED / 登记沉降),不做重建动作 | N-32 P2 / B10:多所有者导致"谁先看到 DISCONNECT 就谁重建"的竞争与重建风暴 |
+| 11 | **监听重建只有一个所有者**(`net_reconcile_tick` 形态③④,自愈收敛阶段 3 合并后;原 `net_listener_pump` 已删);其它位置**只置事实**(置 CLOSED / 登记沉降),不做重建动作 | N-32 P2 / B10:多所有者导致"谁先看到 DISCONNECT 就谁重建"的竞争与重建风暴 |
---
@@ -395,13 +395,13 @@ sequenceDiagram
| `ch395f_get_glob_int_status_all` | **仅 `net_poll`**(F-NET-7 / B3:全局寄存器唯一读者;`net_listen_locked` / `net_connect_locked` / `net_close_locked` / `net_relisten_locked` 的原全局读清已全部删除) | **读 = 清**(中断清除机制,无按位手段)——其他位置读清会焊死 net_poll 的按 Socket 门控 |
| `ch395f_get_sock_int_status` | `net_poll`(逐 Socket SINT);各 open 路径后清残留;事件处理器清 TIMEOUT | **读 = 清** |
| `ch395f_sock_events` | `net_connect_locked`(open 后清残留,F-NET-7 护栏原语);`net_close_locked`(芯片已 CLOSED 快路径只清本 Socket 残留事件) | 只碰**本 Socket** SINT 的护栏原语 |
-| `ch395f_open_socket` / `ch395f_close_socket` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`(含沉降期主动 close);`net_close_locked` 的 CLOSE 已改经 `ch395f_cmd_done` / `ch395f_sock_reset_*`(N34 阶段 3) | TRAP-NET-09:close 后需沉降才能重开 |
+| `ch395f_open_socket` / `ch395f_close_socket` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`(含沉降期主动 close);`net_close_locked` 的 CLOSE 已改经 `ch395f_cmd_done` / `ch395f_sock_reset_*`(自愈收敛阶段 3) | TRAP-NET-09:close 后需沉降才能重开 |
| `ch395f_tcp_listen` / `ch395f_tcp_connect` | `net_listen_locked` / `net_relisten_locked`;`net_connect_locked` | — |
| `ch395f_set_proto_type` / `set_sour_port` / `set_des_ip` / `set_des_port` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`、`net_sendto` | open 之后写端口/IP(open 会复位寄存器);set_des_ip 期望**大端数组** |
| `ch395f_write_send_buf` | `net_send_locked` / `net_sendto` | 单次 ≤1KB(硬件 FIFO 上限,TRAP-NET-16) |
| `ch395f_get_recv_len` / `ch395f_read_recv_buf` | `net_recv_locked`、`net_pending_tick`、`net_pending_settle_close`、`net_recvfrom`、`net_drain_chip_rx`、`handle_disconnect_event`(M3 可疑门 RX 非空判据) | `handle_recv_event` **故意不读**(防竞争,旧机理未证实) |
| `ch395f_get_socket_status` | `handle_disconnect_event`(两处甄别——事件路径**主动 SPI**,缓存必然滞后于刚到达的中断)、`net_relisten_locked`(沉降门)、`net_connect_locked`(诊断回读)。⚠ **周期对账不再直接调**——改读缓存(下行) | 输出 **2 字节**([0]=Socket 状态 [1]=TCP 状态),缓冲必须给 2 字节 |
-| `ch395f_get_sock_state` / `ch395f_sock_state_cached` | 刷新**仅 `net_poll`** 每轮对在用 Socket 调 `get_sock_state`(唯一刷新点);`sock_state_cached` 读缓存 = `net_reconcile_tick`(形态①②③④⑤/U11 判据) | N34 阶段 3:芯片状态唯一事实源;E2 实测单次回读 18.1µs,缓存新鲜度 ≤1ms |
+| `ch395f_get_sock_state` / `ch395f_sock_state_cached` | 刷新**仅 `net_poll`** 每轮对在用 Socket 调 `get_sock_state`(唯一刷新点);`sock_state_cached` 读缓存 = `net_reconcile_tick`(形态①②③④⑤/U11 判据) | 自愈收敛阶段 3:芯片状态唯一事实源;E2 实测单次回读 18.1µs,缓存新鲜度 ≤1ms |
| `ch395f_sock_reset_begin` / `ch395f_sock_reset_poll` | `net_sock_reset_sync`(`net_listen_locked` / `net_connect_locked` open 重试、`net_close_locked`、`net_drop_conn_locked`);`net_poll` 每轮对在用 Socket **步进**(IDLE 槽纯 RAM 判零 SPI) | 驱动复位状态机:读空 RX → CLOSE → 排空事件(quiescent) → 等 byte0=00H;内置 LISTEN 态二段 CLOSE 补发(E2 补充发现);GRACEFUL 100ms 底 / QUARANTINE 400ms 底 |
| `ch395f_cmd_done` | `net_close_locked`(CLOSED 快路径判据) | 命令完成判定(OPEN/LISTEN/CLOSE 三形态,读缓存不额外发 SPI) |
| `ch395f_link_up_cached` / `ch395f_link_change_count` | `net_reconcile_tick`(链路态订阅留痕,F-DRV-13) | 驱动 link 变化计数,net_poll 喂 |
@@ -416,7 +416,7 @@ sequenceDiagram
|---|---|
| 监听恢复策略(节奏/时限/沉降) | `net_listener_try` / `net_relisten_locked` / `net_listener_retry_tick`;常量 `NET_RELISTEN_*_MS`(`net_listener.c` 内);重建决策在 `net_reconcile_tick` 形态③④(下行) |
| 阻塞接收等待语义 | `net_pending_register` / `net_pending_tick` / `net_pending_complete`;常量 `NET_PENDING_WATCHDOG_MS` |
-| 丢中断自愈 + 监听重建策略(合一 tick,N34 阶段 3) | `net_reconcile_tick`(`NET_RECONCILE_PERIOD_MS` / `NET_RECONCILE_COOLDOWN_MS` / `NET_RECON_STABLE_ACCEPT|RELISTEN|DEATH`,①②③④⑤+U11,唯一重建入口=形态③④)+ `handle_disconnect_event` / `handle_connect_event` |
+| 事件未收到自愈 + 监听重建策略(合一 tick,自愈收敛阶段 3) | `net_reconcile_tick`(`NET_RECONCILE_PERIOD_MS` / `NET_RECONCILE_COOLDOWN_MS` / `NET_RECON_STABLE_ACCEPT|RELISTEN|DEATH`,①②③④⑤+U11,唯一重建入口=形态③④)+ `handle_disconnect_event` / `handle_connect_event` |
| 新增一个公共 API | 模式:`net_socket.h` 声明(`NET_NO_OS` 条件编译)→ `net_msg_type_t` 加类型(如需要)→ `net_process_messages` 加 dispatch 分支 → netTask 侧 `_locked` 实现;调用侧复制"参数检查 → 入队 → `net_wait_result` → errno 恢复"骨架 |
| 缓冲布局 / MSS / 重传 / KeepAlive 参数 | `net_init` + `net_config.h` + `ch395f.h`(`CH395F_RECV/SEND_BLOCKS`、`CH395F_TCP_MSS`);改完必须 0 错 0 警构建 + 全测试套件回归 |
| 队列容量 / 消息结构 | CubeMX 生成的 `MX_FREERTOS_Init`(8 槽)+ `net_socket.h` 的 `net_msg_t`(**勿加回传字段**) |
@@ -427,7 +427,7 @@ sequenceDiagram
## 9. 相关文档
- `docs/网络/BSD_Socket_API_使用指南.md` —— 公开 API 语义、示例、常见问题(本文的上半部)
-- `docs/子系统设计/网络Socket监听生命周期设计说明.md` —— **监听生命周期权威设计**(N-32:边沿→电平、单一所有者重建(N34 阶段 3 起为 `net_reconcile_tick` 形态③④)、`net_listener.c` 模块化边界 §10)
+- `docs/子系统设计/网络Socket监听生命周期设计说明.md` —— **监听生命周期权威设计**(N-32:边沿→电平、单一所有者重建(自愈收敛阶段 3 起为 `net_reconcile_tick` 形态③④)、`net_listener.c` 模块化边界 §10)
- `docs/问题记录/CH395F_Trap_Records.md` —— TRAP-NET-01~23 机制细节(本文引用处)
- `docs/网络/Net层阻塞模型架构设计.md` —— 阻塞等待模型的现状架构设计(pending 请求模型 / 五路完成语义)
- `docs/问题记录/错误码传播审计.md` —— E-02 头阻塞与错误码回传全审计
diff --git a/docs/网络/N34-自愈审计.md b/docs/网络/N34-自愈审计.md
deleted file mode 100644
index e17f1904..00000000
--- a/docs/网络/N34-自愈审计.md
+++ /dev/null
@@ -1,2062 +0,0 @@
-# N34 自愈审计(合并文档)
-
-> **本文件由原 `docs/网络/N34-自愈审计/` 目录的 7 份文档合并而成(2026-10-13),原目录已删除。**
-> 合并方式:**忠实合并**——保留全部审计发现(F-NET-1..15 / F-DRV-1..16 / F-MS-1..9)、
-> 逐机制裁决、实施方案与 E0~E13 实验记录;仅统一章节编号、修正交叉引用与配图路径。
->
-> 审计对象:BSD Socket API 的「芯片/本层状态对账与重监听自愈」五层机制
-> (auto_relisten / D10 重试 / TRAP-NET-17 对账 / N-32 pump / TRAP-NET-18 排渣隔离)
-> 及其显式 API(`net_relisten` / `net_resync_listen`)。
-> 方法:三路独立审计汇总(net 层 / 驱动层 / 主流对照)+ 汇总裁决;全部证据带 文件:行号,
-> HEAD `09d6195` 实测。性质:审计 + 实施方案 + 实验记录(阶段 0~3 与 E6 均已完成并转正)。
->
-> | 原文件 | 本文章节 |
-> |---|---|
-> | `网络不稳根因总览.md` | §0 网络不稳根因总览(人话版复盘) |
-> | `net层自愈机制盘点.md` | §1 net 层自愈机制盘点(T1) |
-> | `CH395F驱动层审计.md` | §2 CH395F 驱动层审计(T2) |
-> | `主流方案对照.md` | §3 主流嵌入式 socket 栈对照研究(T3) |
-> | `README.md` | §4 三方汇总裁决与目标架构(T4) |
-> | `实施方案.md` | §5 实施方案 |
-> | `实验方案.md` | §6 实验方案与记录 |
-
-
----
-
-## 0. 网络不稳根因总览(人话版复盘)
-
-> 定位:本文是 N34 自愈审计的**人类可读复盘总览**——把散在 Trap 记录、实验台账、
-> §5 里的根因,用一个贯穿比喻串成一条因果链,并逐环给出已落地的拆法与证据出处。
-> 技术细节与行号证据一律指向本文其余章节与 `docs/问题记录/CH395F_Trap_Records.md`,
-> 此处不重复。适合新成员第一遍读,也适合对外解释"我们的网络为什么曾经时好时坏"。
-
-**一句话回答**:不稳从来不是"网断了",而是**一个会丢门铃、只会礼貌送客的芯片**,
-配上一套**靠门铃驱动、人人插手重建、唯一传达员还会被按在椅子上半分钟**的软件——
-每个环节单看都"正常工作",叠起来就是随机、无日志、时好时坏的"玄学"。
-
-### 0.1 角色表
-
-把 CH395F 芯片想成一个**外国籍传达室大爷**:他会收发信件(TCP 栈),但**门铃会坏**
-(中断会丢)、**只会礼貌送客不会轰人**(close 无 RST)。netTask 是**全楼唯一能进传达室
-的传达员**(所有 CH395F 事件只在 `net_poll` 集中读清)。FTP、对时、UI 推流、IEC104
-是**楼里等信的住户**(各自监听一个端口,但共用这一个传达员)。
-
-### 0.2 七个误会(叠加才成为"玄学")
-
-#### ① 门铃会丢(TRAP-NET-17:丢中断)
-
-快速复连时芯片本应"按门铃"(INT# 拉低)通知有新来访/客人离开,但门铃**会哑**——
-人已经站在屋里,铃没响。传达员只靠门铃干活,于是本层状态**静默滞留**:端口其实
-死了,日志里**一个错都不报**。当年"待机后 UI 复连 2/10 永久拒连、FTP 第 29 轮自己
-死掉"就是它——不是断网,是**没人报信**。裸机芯片台架(剥离 OS/net/FTP)独立复现,
-证明这是芯片天性,不是固件 bug(见 `docs/网络/N34-自愈审计.md` §4 结论 1)。
-
-#### ② 门铃总闸是"看一眼就清零"(TRAP-NET-20 B3)
-
-全局中断寄存器是**一整排灯,读一次全部熄灭**。历史上 open/relisten 里有两处
-"丢弃式读一遍"——等于**查自己门铃时把全楼别的住户的门铃线也拔了**,别人的
-CONNECT/DISCONNECT 从此永久失踪。这是"自家制造的丢中断"放大器。
-
-#### ③ 柜台和货梯是同一台(单连接模型)
-
-监听 fd 就是数据 fd——**商店柜台平时兼货运电梯**。住户看到"客人走了"(EOF)习惯性
-`net_close` → **把柜台连电梯一起拆了** → 端口"连接被拒"。而芯片侧其实已自动恢复
-(auto_relisten),应用这一刀纯属帮倒忙。注意:单连接复用本身**不是**错误(主流硬
-TCP 栈全部同形),错的是住户不懂模型就动手。
-
-#### ④ 三个临时工都在修电梯(多所有者 relisten)
-
-accept、recv、pending 三处都会"顺手重建监听",谁先看到断连谁动手 → 互相踩脚:
-重建风暴、慢消费者还没读完的 EOF 被吞、新客户端数据被旧会话静默吸收。修得越多越乱。
-
-#### ⑤ 传达员被按在椅子上(队头阻塞)
-
-一个挂起的对时客户端不发数据 → 旧代码 **30 秒阻塞收**;而 netTask 是**全楼唯一
-传达员**——这 30 秒里 FTP 的开门请求、UI 的连接请求全部排队冻结(E-02 实测 FTP
-banner 迟到 25~30 s)。各处还有 500 ms 盲等忙等,传达员频繁"冻结半秒"。
-
-#### ⑥ 僵尸客人赖着不走(E13:网卡闪断)
-
-PC 网卡闪断一下 → PC 内核**悄悄把自己的连接记录撕了**(不发 FIN),芯片那头还认为
-"客人正在屋里聊天"(ESTABLISHED)。单连接模型下**唯一的柜台被这个不存在的客人永久
-占用**,真客人来了只得到"连接被拒"。当时看起来像"设备网络莫名死了",其实是
-**和幽灵做生意**。
-
-#### ⑦ 大爷只会礼貌送客,不会轰人(close 无 RST,手册定案)
-
-芯片的 close 只发 FIN("您请回吧")。对面**内核活着但程序挂死**时——内核礼貌回
-ACK,但客人**就是不起身**——芯片永远站在门口等(FIN_WAIT_2 长尾,E-02 定案:监听
-恢复受限于对端 close)。它**没有"轰出去"(RST)这个动作**——手册全文 RST 只出现在
-listen 占用与多连接两处。这是芯片天性。
-
-#### ⑧ 垃圾不清,越扫越脏(残渣风暴态)
-
-上面每一条的残骸(迟到的 DISCONNECT 锁存、半死 Socket、粘滞中断位)都会**带进下
-一条连接**。所以故障率不是常数:残渣少时 2%,堆起来能摆到 54%(E8 实测)——同一份
-固件时好时坏,看起来像"玄学"。
-
-### 0.3 全景图
-
-
-
-### 0.4 僵尸会话时间线(误会⑥ 的完整一生)
-
-```mermaid
-sequenceDiagram
- participant PC as PC(网卡+内核)
- participant CH as CH395F(大爷)
- participant NT as netTask(传达员)
- Note over PC,CH: 会话正常通信(ESTABLISHED)
- Note over PC: 网卡闪断(驱动重启/电源管理)
- PC--)CH: 内核撕掉 socket,不发 FIN(静默)
- Note over CH: 仍认为 ESTABLISHED,唯一柜台被幽灵占用
- Note over NT: 新连接到达 → 连接被拒(端口"死了")
- CH->>PC: keepalive 探测(空闲 60s 起,每 5s,共 3 次)
- PC--xCH: 无人认领 → 探测无响应
- CH->>NT: TIM_OUT 中断(≈30~90 s)
- NT->>CH: QUARANTINE:sock_reset 排渣(读空→CLOSE→排空→确认)
- NT->>CH: relisten → LISTEN
- Note over NT: 端口恢复,新客人正常进门
-```
-
-### 0.5 环 → 拆法 → 落点(对照表)
-
-| 环 | 拆法(已落地) | 证据/落点 |
-|---|---|---|
-| ① 丢中断 | 单一 reconciler:100 ms 巡逻对账五种形态,判据读驱动状态缓存 | TRAP-NET-17;§6 E2(回读 18.1 µs 背书);§5 阶段 3 项 1 |
-| ② 总闸读清 | 全局寄存器唯一读者=net_poll(F-NET-7);open/relisten 只清本 Socket SINT | TRAP-NET-20 B3;§5 阶段 0/2 |
-| ③ 拆柜台 | 铁律"EOF 不得 close 监听 fd" + 新 API `net_drop_conn`(拆会话保监听) | 生命周期设计说明 §10.10;§6 E-02 |
-| ④ 多所有者 | 重建收敛唯一所有者(net_listener + reconciler),其余只置事实不动手 | §4.0 结论 4(不变量 II);§5 阶段 3 项 1 |
-| ⑤ 队头阻塞 | 忙等统一 `net_sock_reset_sync` 有界排渣(600 ms 硬顶);recv 非阻塞消息化 | §6 E-02(banner 39 ms);阶段 1.5 |
-| ⑥ 僵尸会话 | keepalive 60/5/3 + 芯片 TIMEOUT → QUARANTINE → 重建(30~90 s 自愈) | §6 E11/E13(用户判断"不该依赖冷启动"被证实) |
-| ⑦ 无 RST | 定案入 TRAP-NET-07:兜底靠查岗不靠 RST;`net_drop_conn` 本地拆除 + 对账③④ 认领 | TRAP-NET-07 定案更正;§6 E-02 收口 |
-| ⑧ 残渣风暴 | `sock_reset` 标准排渣(读空→CLOSE→排空事件→byte0=00H 确认)+ M3 矛盾态门 | TRAP-NET-18/-20;§5 M3/M5(E8/E9 风暴态免疫) |
-
-### 0.6 一句话总结
-
-原来的"不稳" = 芯片天性(丢门铃、无 RST)× 软件形态(靠门铃驱动、人人插手、
-唯一传达员会冻结)× 残渣复利(脏态带进下条连接)。N34 做的事不是修某一个 bug,
-而是把每一环改成**不依赖门铃(100 ms 对账)、只有一个主人(单所有者)、永远不冻结
-(有界排渣+消息化)、开张前扫垃圾(sock_reset)**——所以现在的"稳"是结构性的。
-
-> 遗留观察项:E6 正在挂机量化"RX 非空但 readable=0"(RECV_OK 中断丢失的数据面
-> 形态)在生产是否真实发生(`NET_U15_PROBE`,§6 E6 节)——命中>0 则上层
-> 32 轮盲收内化进 net 层(阶段 3 项 4);=0 则保留现状登记观察。
-
-
----
-
-## 1. net 层自愈机制盘点(T1)
-
-> 审计对象:`Drivers/BSP/NET/net_socket.c`(3671 行)与 `net_listener.c`(554 行)/ `net_listener.h`。
-> 方法:全文通读 + 调用点 grep + git 考古(`git log -S`)+ 问题记录与设计文档对照。纯只读,未改任何代码。
-> 行号均为本工作树 HEAD(`09d6195`)实测行号。
-> 参照文档:CH395F_Trap_Records.md(TRAP-NET-09/-17/-18/-20 与 2026-10-11 寄存器审计章节 B1~B10 条目)、
-> 网络Socket监听生命周期设计说明.md(B10 根治设计,含 P0/P1/P2 落地记录与模块化边界)、
-> BSD_Socket_API_使用指南.md(五层自愈口径)。
-
-### 1.1 机制 × 属性总表
-
-核心七机制(用户指定盘点范围):
-
-| # | 机制 | 代码位置 | 触发条件 | 改本层哪些状态 | 发哪些芯片命令 | 定时器/去抖/冷却 | 重叠/互为兜底的对象 | 根因分类 | 初步裁决 |
-|---|---|---|---|---|---|---|---|---|---|
-| 1 | **auto_relisten**(监听常驻标志) | 置位 net_socket.c:1119(`net_listen_locked`);字段 net_types.h:172;消费点 net_socket.c:1316/1924-1938/2136-2162/1483、net_listener.c:501 | `net_listen` 成功即置 1;此后任何"会话已结束"路径(DISCONNECT/TIMEOUT/对账①)把 fd 置 CLOSED 后,由消费点或 pump 依据该标志重建 | 本身不改状态,是**门控标志**;`net_close`(memset net_socket.c:2351)与 `net_init`(:244)清零 | 无(纯标志) | 无 | 是所有重建路径(机制 2/4/6 与 accept/recv/pending 三处即时重建)的共同前提门 | (a) 芯片永不自动回 LISTEN(手册无此条款,设计文档认定;单连接复用模型锁死) | **保留**(模型基石,不可删) |
-| 2 | **D10 非阻塞重监听重试** `net_listener_retry_tick` | net_listener.c:394-432;登记 net_listener.c:374-378;常量 :77-79 | 任何 `net_listener_try` 单次重建失败(open 失败 / LISTEN 失败 / 隔离未满)即登记 `s_relisten_wait`,到点重试 | 成功经 `net_relisten_locked` 置 `state=LISTENING`(net_listener.c:233)、清 `released_abnormal`、转登 `last_peer_port`、清 `lost_conn_seen`、清对端簿记(:239-255);**刻意不清 `session_dead`**(:234-238 契约注释) | 隔离段见机制 5;正常段:SET_PROTO_TYPE、SET_SOUR_PORT、OPEN_SOCKET、TCP_LISTEN(:207-230);open 失败补 CLOSE_SOCKET(:218);成功尾读本 Socket SINT 读即清(:262) | 重试间隔 30ms(:77)、放弃时限 2s(:79,自首次尝试);无去抖(单次尝试);**L3 保险**:同 fd 有在途挂起 recv 则跳过(:405-408,经 net_socket.c:2202 `net_pending_active`) | 是机制 4(pump)与三处消费者即时重建的**执行器**(它们调 `net_listener_try`,失败落到这里);"2s 放弃"并非终态——pump 冷却后会再次触发(见 F-NET-4) | (a) 芯片 close 后重开需沉降(TRAP-NET-09 PHY 形态 + TRAP-NET-20 裸机台架实证拆链窗口 >2s 不定长);旧实现 osDelay(500) 忙等属 (b) 已修(e5733b3) | **保留**;"give up" 语义需简化(F-NET-4) |
-| 3 | **TRAP-NET-17 周期对账** `net_reconcile_tick` | net_socket.c:548-641;常量 :544-546 | 每 200ms 对**认领范围内**(仅本层 ESTABLISHED / LISTENING,:579-581)的 fd 回读芯片两字节状态:① 本层 ESTABLISHED 而芯片 SOCKET_CLOSED 或 TCP_CLOSED(:588-590);② 本层 LISTENING 而芯片 TCP_ESTABLISHED(:591-592) | ① 复用 `handle_disconnect_event`(:633)→ 置 `session_dead=1`、`last_peer_port`、`state=CLOSED`、可能登记 GRACEFUL 沉降;② 复用 `handle_connect_event`(:638)→ 置 ESTABLISHED、清 `session_dead`、记对端 | GET_SOCKET_STATUS(每认领 fd 一条,:586);② 前 GET_REMOT_IPP 幽灵甄别(:597-602);后续按事件路径的命令 | 周期 200ms(:544)、连续 2 次失配去抖(:545,≈400ms)、动手后 1s 冷却(:546) | ② 与 accept 路径内联探针 `net_lost_connect_probe`(net_socket.c:1228-1267)职责重叠(同一失同步双兜底,TRAP-NET-20 定案"探针快路径 + 对账兜底网");① 与 `net_resync_listen` 语义相邻但判据不同(见 F-NET-2);原③④ 已移交 pump(:536-537/606-607) | (a) 芯片丢 CONNECT/DISCONNECT 中断(TRAP-NET-17 生产实测 + TRAP-NET-20 裸机台架独立实证,**与 B3 固件 bug 无关**);幽灵态 (a) | **保留**;建议扩展①(F-NET-2) |
-| 4 | **N-32 监听器 pump** `net_listener_pump` | net_listener.c:465-553;常量 :437-441 | 每 100ms 对**监听型 fd**(in_use + auto_relisten + STREAM + local_port≠0,:501-507)且本层处于 LISTENING/CLOSED(:509-513)者回读芯片:① 本层 LISTENING 而芯片不在 LISTEN → 重建;② 本层 CLOSED 而芯片不在 LISTEN → 重建;③ 本层 CLOSED 而芯片**正在** LISTEN(U11)→ 只重同步 | ③:仅 `state: CLOSED→LISTENING`(:528)+ 转登 `last_peer_port`、清 `lost_conn_seen`(:532-533),**不发任何芯片命令**;①②:经 `net_listener_try` 走机制 2 的状态改动;`session_dead` 不清(:462-463 契约) | 每监听型 fd 一条 GET_SOCKET_STATUS(:516);③ 零命令;①② 同机制 2 | 周期 100ms(:437)、连续 2 次去抖(:440)、1s 冷却(:441) | 本层**唯一周期性重建决策者**(不变量 II,设计文档第 4 节);与三处消费者即时重建(accept net_socket.c:1327 / recv :1938 / pending :2162)构成"周期兜底 + 消费者驱动"双层——该分工是 P2a 台架实测推翻原计划后的定案(删消费者驱动会致 `[WD] task stall`) | (c) 设计缺口(多所有者竞争)的**架构解**;其底层需求(芯片不回 LISTEN + 事件会丢)为 (a) | **保留**(这就是"内聚"的落点,不是补丁) |
-| 5 | **TRAP-NET-18 排渣隔离** `net_drain_chip_rx` + 隔离段 | 排空 net_listener.c:102-121;隔离段 net_listener.c:149-204;登记 net_listener.c:282-289(`net_listener_settle`),登记点 net_socket.c:3430(GRACEFUL,仅 `recv_bytes≠0`)/:3523(QUARANTINE) | 异常释放(芯片 TIMEOUT,net_socket.c:3512-3524)登记 QUARANTINE;正常释放(芯片 DISCONNECT 且本连接收过数据,:3424-3432)登记 GRACEFUL;下一次 `net_relisten_locked` 进入隔离段 | 隔离期满清 `s_relisten_quarantine/_settle_start/_settle_dead`(:199-203);不改 socket 状态(状态改动仍由机制 2 成功尾做) | 隔离期内每轮:GET_RECV_LEN + READ_RECV_BUF(排空,:172→:107-120)、CLOSE_SOCKET(主动关,:174)、读本 Socket SINT 读即清(:176)、**读全局中断寄存器**(:178,见 F-NET-7)、GET_SOCKET_STATUS(:180) | QUARANTINE 最短 400ms / GRACEFUL 最短 100ms / 上限 800ms 强制放行(net_listener.c:53-55;上限必须 < 2s 重试时限,实测 2000ms 会把监听耗死) | 与 `net_close_locked` 的"close→等 CLOSED→排空中断"循环(net_socket.c:2326-2345)是同一芯片契约的**两个变体实现**(下沉候选,见 F-NET-9);GRACEFUL 的 `recv_bytes≠0` 门是为保护 TRAP-NET-17 快速复连时序而设(两机制互相制衡) | (a) FUN_PARA bit3=1(0x08,D4 定案必须保留)下芯片保留现场 + 在途数据在 TIMEOUT 之后才落缓冲 + 残留 DISCONNECT 投递给下一条连接(TRAP-NET-18 两阶段台架实证;0ms/30ms 沉降无效、100ms 通过) | **保留**;"close+排空+等 CLOSED"原语可**下沉**驱动层合并(F-NET-9) |
-| 6 | **net_resync_listen / net_relisten** 显式自愈 API | API net_socket.c:3026-3066 / :3080-3120;分派 :774-799 / :800-814 | 应用层判定"连接已死/监听器可能失位"后主动调用;**生产代码零调用者**,唯一调用者 test/board/net_latency_test.c:783-784/787/800(Phase 13 台架,产品构建不启用) | RESYNC:仅本层 `state: ESTABLISHED→LISTENING` + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv(:789-797),**不发芯片命令**;RELISTEN:经 `net_listener_try` 无条件芯片侧重建(机制 2 全套) | RESYNC:无;RELISTEN:机制 2 全套命令(含隔离段) | 无自身定时;RELISTEN 失败落 D10 重试 | RESYNC 与 pump③(U11)语义相邻但覆盖的本地状态不同(ESTABLISHED vs CLOSED);RELISTEN 与 pump①② 完全重叠且**更危险**——对健康监听器无条件 open→USE→close 会造成 ~1s 监听中断(net_socket.c:3078 注释自认) | RESYNC 针对的"芯片静默回收"为 (a)(run8 台架实证,当时 FUN_PARA 已是 0x09);但 API 形态属 (c) 时代遗留(上层镜像芯片状态、手动修);RELISTEN 属 (c) 冗余 | **可删**(生产路径):RESYNC 语义并入对账①扩展后删 API;RELISTEN 直接删(F-NET-2/F-NET-3) |
-| 7 | **L3 挂起 recv**(pending)及其交互保险 | 结构 net_socket.c:184-193;登记 :2024-2063;完成 tick :2074-2190;了结 :1985-2007;取消 :2237-2256;close 前了结 :2267-2298;查询 :2202-2208 | 阻塞 recv 无数据即登记;`net_pending_tick` 每轮按 ①数据 ②到点 ③断连(`CLOSED ∥ session_dead`,:2136)顺序了结 | ③ 断连分支:排空后按 `released_abnormal` 回 CONNRESET 或 EOF(0)(:2164-2174);`state==CLOSED` 且 auto_relisten 时**消费者驱动即时重建**(:2159-2162);close 路径先 settle(排空或取消,必通知,:2316-2318);`net_init` 重建前 `cancel_all`(:241) | GET_RECV_LEN + READ_RECV_BUF(①③ 读数据);③ 即时重建走机制 2 命令 | 每 fd 至多一个挂起(:2038-2041 防覆盖挂死);deadline=登记+预算(默认 30s,net_config.h:39);**看门狗**:超 deadline 后 1s 强制了结 + DBG_ERROR(:182/:2181-2188);与机制 2 的交互保险 = retry_tick 的 `net_pending_active` 跳过(net_listener.c:405-408) | 与机制 2 互为保险(挂起在途则 D10 不动该 fd);③ 的即时重建与 recv_locked DONTWAIT 的即时重建同型(:1936-1938);`cancel_all`(net_init)与 tick 的"fd 已关闭兜底"(:2092-2096)双保险 | 阻塞模型演进(E-02 头阻塞修复),非严格自愈;③ 的排空+EOF 语义对应 (a)(旧观察注:FIN 先于数据、bit3=1 保留现场);看门狗属 (c) 完成逻辑缺位的 tripwire | **保留**(含看门狗,留痕成本极低);它是"消费者驱动重建"三处之一,P2a 已定案不可删 |
-
-配套机制(不在七项内但参与同一失同步网,盘点完整性所需):
-
-| 机制 | 位置 | 一句话属性 | 根因 | 裁决 |
-|---|---|---|---|---|
-| `net_lost_connect_probe`(TRAP-NET-20 双信源探针) | net_socket.c:1228-1267,调用 :1298-1300 | accept 轮询内联补 accept;去抖 2 轮(:1214)无冷却;幽灵甄别 `last_peer_port` | (a) 拆链窗口丢 CONNECT(裸机台架 15/15) | 保留 |
-| closed-with-data accept | net_socket.c:1305-1335 | CLOSED + RX 有数据 → 接受"已关但可读"连接排空(bit3=1 排空契约) | (a)(TRAP-NET-20 三次定案:425 真根因是我方 relisten 自毁缓冲) | 保留 |
-| `session_dead` 电平(N-32 P1 不变量 I) | net_types.h:192-206;置位 net_socket.c:1723/3439/3535;清零 :3310/3347;不清 net_listener.c:234-238 | "会话已结束"从边沿改粘滞电平,消费者不再被抢先 relisten 饿死 | (c) 设计缺口的**架构解** | 保留(这是根治本身) |
-| `released_abnormal` | net_types.h:174-180;置位 net_socket.c:3529/3434 | 异常/正常释放区分 → 挂起 recv 回 CONNRESET 还是 EOF | (a) 协议边界(在途 FIN 与正常完成不可区分,TRAP-NET-18 硬化节留痕) | 保留 |
-| `net_listen_locked` 3×osDelay(500) 阻塞重试 | net_socket.c:1092-1108 | 首次监听路径仍忙等(P2b 暂缓项),与 D10 非阻塞模式并存 | (b) 时代遗留(D10 已修 relisten 侧,listen 侧未统一) | 简化(统一走 `net_listener_try`) |
-| `net_close_locked` 500ms 等 CLOSED 忙等 | net_socket.c:2326-2345 | 关闭热路径同步忙等(P2b 暂缓项) | (c) 同步等待设计债;台架实测危害理论性(设计文档 P2b 决策) | 简化(暂缓有据,留观察) |
-| 陈旧 DISCONNECT 甄别(D20) | net_socket.c:3400-3415 | ESTABLISHED 收 DISCONNECT 时回读芯片,TCP 仍 ESTABLISHED 判陈旧丢弃 | (a) TRAP-NET-12 滞后锁存 | 保留 |
-| 代次号 `s_net_generation` | net_socket.c:49/358-374;递增 net_task.c:145 | 网络重建后服务层重建监听的依据(应用层重建所有者通道) | (c) 但边界清晰(芯片复位后 fd 全灭是事实) | 保留 |
-
-### 1.2 根因分类汇总
-
-**(a) 芯片真实行为(手册条文或裸机台架实证,无法从根上消除)——对应机制必须保留:**
-
-| 芯片行为 | 证据 | 支撑的机制 |
-|---|---|---|
-| 连接结束后芯片永不自动回 LISTEN(单连接复用模型下监听槽位被消耗) | 设计文档第 2.1 节引手册;官方 EXAM10 例程在事件现场当场重建 | auto_relisten、pump、D10、resync 语义 |
-| 快速复连/拆链窗口内丢 CONNECT/DISCONNECT 中断 | TRAP-NET-17 生产实测;**TRAP-NET-20 裸机台架剥离 OS/net 层仍复现**(关键:证明与固件层 bug 无关) | 对账①②、探针、pump |
-| 拆链收尾窗口变长且不可观测(>2s 不定长),窗口内假报 ESTABLISHED(幽灵态,对端端口=旧连接) | TRAP-NET-20 定案实验 | 幽灵甄别 `last_peer_port`、去抖+冷却纪律 |
-| FUN_PARA bit3=1(0x08,D4 定案保留):DISCONNECT/TIMEOUT 后芯片不关 Socket 不清缓冲(停 0x05/0x00),MCU 必须排空后显式 close | 手册 §5.52②/§5.23;TRAP-NET-20 三次定案判别探针(recv_len=1027 完整载荷) | closed-with-data accept、TRAP-NET-18 排渣、close_locked 排空循环 |
-| 中止时段的在途数据在 TIMEOUT **之后**才落进 RX 缓冲(~2KB 残渣被下条连接读走) | TRAP-NET-18 一阶段(RST 10/10 复现→修复 10/10) | QUARANTINE 400ms + 持续排空 |
-| 上一条连接残留 DISCONNECT 会在复用同 fd 的下一条连接 accept 后 10~20ms 投递 | TRAP-NET-18 二阶段(0ms/30ms 沉降无效、100ms 通过) | GRACEFUL 100ms(仅收过数据时登记) |
-| close 后重开 Socket 需沉降(PHY 形态 + 拆链形态) | TRAP-NET-09;TRAP-NET-20 台架(固定沉降 100~3000ms 均证伪"等就好") | D10 到点重试(而非固定延时) |
-| 芯片可静默回收连接回监听且不发 DISCONNECT | run8 台架实证(当时 FUN_PARA=0x09,bit3 已=1,git 核实) | **阶段 1 起由对账⑤自动认领**(原仅 `net_resync_listen` 覆盖 = F-NET-2 缺口,已闭环) |
-| 全局中断寄存器整体读即清、无按位清除;SINT 粘滞到被读 | 手册 §5.15;TRAP-NET-12 | net_poll 集中读清纪律;也解释了为何"丢弃式读全局"有害(F-NET-7) |
-
-**(b) 早期固件 bug(寄存器数值/引用/时序误用)——根因已修,评估补丁是否仍必要:**
-
-| 早期 bug | 修复 | 曾支撑的补丁 | 根因修复后补丁是否仍必要? |
-|---|---|---|---|
-| B3:listen/relisten 对全部 8 个 Socket 读 SINT + 丢弃式读清全局(多连接时代调试遗留,git 考古 e862009bc→319d00e3→db64e6acf) | 4f2cc8b(2026-10-12)收窄为只清本 Socket | TRAP-NET-17 对账在生产中的部分命中 | **仍必要**:TRAP-NET-20 裸机台架(无 net 层代码)独立实证芯片自身丢中断;但 B3 修复后对账在生产日志的命中数应显著下降——可台架统计验证(F-NET-8) |
-| `get_socket_status` 曾给 1 字节缓冲(栈越界、状态误读) | e5733b3 | 曾可能污染对账/甄别读数 | 已修,无遗留补丁 |
-| TRAP-NET-15:FUN_PARA bit0=1(保留位,无出处) | d758d5c 定案 0x08 | 多 Socket 模式误判相关 | 已修;对自愈机制无直接影响 |
-| B2:`ch395f_tcp_connect` 返回值被丢弃 → CONNECTING 滞留 30s | 794a688 + a844241(命令级失败置 session_dead) | 无独立补丁,靠 recv 超时暴露 | 已修,无遗留补丁 |
-| relisten 旧实现 osDelay(500) 忙等(全网冻结 500ms) | e5733b3(D10 非阻塞化) | — | 忙等已修;**同族的 `net_listen_locked` 3×500ms 与 `net_close_locked` 500ms 忙等仍在**(P2b 暂缓,F-NET-5) |
-| B1:服务器侧 KeepAlive 使能(按手册补的"未证明复杂度") | 9a45be1 撤回(实测芯片 2s 重传超时已覆盖静默死亡) | — | 正面案例:根因复核后主动删除,应作为本次审计的裁决范式 |
-| **U12(未修)**:relisten 的 TCP_LISTEN 失败不 close 芯片 Socket(与 open 失败路径不对称) | 未修(设计文档 P0 登记项未勾选) | 靠下一轮重试 open→USE→close 兜住 | 见 F-NET-6 |
-
-**(c) 设计缺口(本层镜像芯片状态导致必须对账/多处所有者)——可由架构消解(多数已消解):**
-
-| 缺口 | 现状 |
-|---|---|
-| 重建多所有者(曾 12 个) | P2a pump 收敛周期性路径;消费者驱动 3 处经台架定案保留;显式 API 2 处(RELISTEN/RESYNC)与 net_can_accept 旧门控**已于阶段 1 删除**(F-NET-2/3 ✅) |
-| EOF 边沿化(抢先 relisten 吞 EOF) | P1 `session_dead` 电平化已消解 |
-| 上层直接读 net 层状态镜像 | `lftpd_inet_accept_timeout` 直读 `p_sock->state`(lftpd_inet.c:117)、`net_listener_armed` 用 `readable` 代理"RX 可能有残留"(net_socket.c:1480-1485)、`net_can_read` 自认"对账不含 RX 非空但 readable=0"(:1500-1501)迫使上层保留每 32 轮兜底 recv(ui_net_sink.c:552-558、iec104_serv.c:246-253)——**第三层兜底散在应用层**(F-NET-12) |
-| 应用层仍是重建所有者 | ~~time_sync 每会话 close 监听 fd 全量重建~~(**阶段 1 已修** `b8c6a3d`:同 fd 跨会话常驻);lftpd 应用层阻塞重试已删(阶段 1);iec104 `s_drop_conn` close 监听重建(协议违例场景,有意例外)(F-NET-13) |
-| fd 别名(监听=连接) | 硬件上限(多连接模式不支持多监听,D1 出局),不可消解;只能靠不变量 I/II 约束 |
-
-### 1.3 编号发现
-
-**F-NET-1 · 重建入口实测 5 处,与设计文档定案一致;"唯一重建入口"仅指周期性**
-证据:`net_listener_try` 调用点共 4(阶段 1 删 `NET_MSG_RELISTEN` 后)——net_listener.c:552(pump,唯一周期性)、net_socket.c:1327(accept CLOSED 分支)、:1938(recv DONTWAIT EOF 分支)、:2162(pending_tick ③)。后三处为"消费者动作触发",P2a 台架实测删除会把 CLOSED 窗口拉长到 pump 周期+隔离期并触发 `[WD] task stall`(设计文档第 9 节 P2a 修正记录)。
-影响:这不是缺陷,是**已实测裁决的重叠**;但"唯一重建入口"的表述在 BSD 指南与注释中易被读成"仅 pump 一处",误导后续维护者删消费者驱动路径。
-建议:保留现状;文档口径统一为"唯一**周期性**入口 + 三处消费者驱动"。
-
-**F-NET-2 · "本层 ESTABLISHED 而芯片已回 LISTEN"失同步无任何自动机制覆盖;唯一解药 API 生产零调用**
-证据:对账①判据要求芯片 SOCKET_CLOSED 或 TCP_CLOSED(net_socket.c:588-589),芯片回 LISTEN(0x05/0x01)时不命中;对账②要求本层 LISTENING(:591);pump 只裁本层 LISTENING/CLOSED(net_listener.c:509-513);`handle_connect_event` 对本层 ESTABLISHED 的新 CONNECT 中断两个分支都不命中、静默丢弃(net_socket.c:3306 起)。该场景为芯片真实行为(run8 实证"芯片静默回收、监听器健康",git 核实当时 FUN_PARA=0x09 即 bit3=1)。现仅 `net_resync_listen`(net_socket.c:774-799)能修,而其生产调用者为零(仅 net_latency_test.c:783)。
-影响:一旦生产出现"静默回收 + 无 DISCONNECT",端口将滞留假 ESTABLISHED,新连接被芯片接住但本层无感知,直到下一次应用 close 或网络重建。
-建议:把该形态并入对账①——本层 ESTABLISHED 且芯片 TCP=LISTEN → 按 RESYNC 语义只改本层状态(不发芯片命令,纪律同 pump③ U11 与 :785-788 注释);并入后 `net_resync_listen` API 可删(见 F-NET-3)。
-**✅ 已实施(阶段 1,`b8c6a3d`)**:对账判据新增 **bad=5**(本层 ESTABLISHED 且 `st[0]!=SOCKET_CLOSED && st[1]==TCP_LISTEN`)→ RESYNC 语义(state→LISTENING + `last_peer_port` 转登 + `lost_conn_seen` 清零 + `net_pending_cancel(CONNRESET)`),不发任何芯片命令;手动 API 同批删除。台架验证见 §6 E10。
-
-**F-NET-3 · `net_relisten` / `net_resync_listen` / `net_can_accept` 三个 API 是冗余或过期面**
-证据:`net_relisten`(net_socket.c:3080)与 `net_resync_listen`(:3026)生产零调用(grep 全仓仅 test/board/net_latency_test.c:776-801);`net_relisten` 对健康监听器无条件 open→USE→close,自认造成 ~1s 监听中断(:3078 注释),与 pump③"芯片在听绝不重下发"的 U12 教训直接冲突。`net_can_accept`(:1427)唯一生产调用者 iec104_serv.c:227。
-影响:公开 API 面越大,上层"手动自愈"路径越多,正是用户怀疑的"补丁式代码难维护"的来源;`net_relisten` 是 footgun。
-建议:`net_relisten` 删除(台架 Phase 13 改用 pump 语义或测试专用内部入口);`net_resync_listen` 在 F-NET-2 并入对账后删除;`net_can_accept` 在 F-NET-4 迁移后删除。
-**✅ 已实施(阶段 1,`b8c6a3d`)**:三 API 与 `NET_MSG_RELISTEN` / `NET_MSG_RESYNC_LISTEN` 消息类型全部删除(MCU + sim 双端),生产/测试调用者归零;Phase 13 的自愈段交回 net 层自动机制。**⚠ 遗留铁律**:门控迁移必须给 `net_listener_armed` 补 ESTABLISHED 放行,见 F-NET-4 实施注。
-
-**F-NET-4 · iec104(:2404)仍用过期门控 `net_can_accept` → 丢 CONNECT 探针对该端口从不运行**
-证据:iec104_serv.c:227 以 `net_can_accept`(只认 ESTABLISHED,net_socket.c:1434)门控 accept;`net_lost_connect_probe` 只在 `net_accept_locked` 内 LISTENING 态运行(:1298-1300),门控挡在门外则永不触发——与 N-32 P1 为 :7003 修复的"探针失效"完全同型(ui_net_sink.c:521-526 注释记载原委)。:2404 的丢 CONNECT 只剩对账②兜底,而②带 400ms 去抖 + 1s 冷却(TRAP-NET-20 已实证该节奏对短命监听器"救不全")。
-影响:104 上行端口的丢中断恢复被拖慢到秒级;三个常驻监听里 :21/:8888 无条件轮询 accept、:7003 已换 `net_listener_armed`,唯独 :2404 漏换。
-建议:iec104 门控换 `net_listener_armed`(双端 sim 已有实现);随后 `net_can_accept` 可退役,消除"两个相似门控语义不同"的长期困惑。
-**✅ 已实施(阶段 1,`b8c6a3d`)**:门控已迁 `net_listener_armed`,`net_can_accept` 三处(MCU / 头文件 / sim)删除。
-**⚠⚠ 实施中发现的坑(必读,§6 E10 有完整证据链)**:`net_listener_armed` 首版只放行 LISTENING / CLOSED+残留,**漏放行 ESTABLISHED**——而单连接模型下"连接已到、应用未 accept"的电平正是 ESTABLISHED(CONNECT 处理把监听 fd 自身转数据通道),也就是旧 `net_can_accept` 唯一放行的状态。后果:i104 200ms 任务周期抢不过对账② 的 ~400ms 去抖,对账②先 standalone accept 转 ESTABLISHED → armed=0 → 应用整场不 accept,**iec104 18/18 全 FAIL**(127s 跑满超时;设备日志:`sock1 standalone accept` 后整场无 `i104: accepted`,直至对端 FIN 才由 closed-with-data 救回)。ui_net_sink 之所以不受影响,是 10ms 周期 + `net_accept_locked` 自带 `net_poll` 通常在状态翻转前就拿到 fd——**属竞态幸存,不是设计正确**。修复:armed 放行 ESTABLISHED(此态 accept 必返 fd,一次调用即离开,无 RPC 风暴风险);修复后 18/18 PASS 且耗时 127s→8.3s。
-
-**F-NET-5 · 两套重监听重试策略并存:D10 非阻塞 vs listen 路径 3×osDelay(500) 阻塞**
-证据:net_listener.c:71-79(D10,注释明言旧 osDelay(500) 忙等致全网冻结 500ms、实测恢复 588ms);但 net_socket.c:1093-1104 `net_listen_locked` 仍是 open 失败→close→osDelay(500)→重试 ×3(最坏 netTask 冻结 1.5s)。设计文档 P2b 已登记该忙等并**台架依据暂缓**。
-影响:同一芯片沉降问题两套答案;首次监听/PASV 监听落在沉降窗时 netTask 停摆(回执全链排队)。
-建议:裁决"简化"——`net_listen_locked` 的失败路径改走 `net_listener_try` 登记 + D10 到点重试(P2b 范围,非紧急,但两套策略不应长期并存)。
-
-**F-NET-6 · U12 不对称仍在:TCP_LISTEN 命令失败不 close 芯片 Socket**
-证据:net_listener.c:214-222 open 失败→close→交重试;:227-230 LISTEN 失败→直接返 BUSY,**不 close**;net_socket.c:1112-1114 `net_listen_locked` 同样不 close。设计文档第 5 节 P2 曾列"补 U12 的 close 对称",第 9 节 P0 登记 U12 进 Trap 记录的复选框未勾选,代码亦未修。
-影响:LISTEN 失败后芯片处于 OPEN 但未监听;下一轮重试 open 返 USE 才走 close 分支兜回(多一轮 30ms + 一次 USE 竞态面);与 pump③ 防的"把正听着的 Socket 关掉"是同源机理的反向形态。
-建议:小修——两处 LISTEN 失败补 `ch395f_close_socket`,与 open 失败路径对称;同时把 U12 正式登记进 Trap 记录(P0 欠账)。
-
-**F-NET-7 · 三处无条件"读清全局中断寄存器"仍在,会搁浅其它 Socket 的挂起事件——对账/pump 因此仍部分背锅**
-证据:net_connect_locked net_socket.c:1668-1670、net_close_locked :2332-2334、relisten 隔离段 net_listener.c:176-178。B3 修复(4f2cc8b)只收窄了 listen/relisten 成功尾两处;Trap 记录 B3 行残余注记明确点名这三处"仍会搁浅(延后、非销毁)其它 Socket 挂起事件——它们是 TRAP-NET-12/-18 指定的两级预清,本次不动"。
-影响:全局寄存器整体读即清(手册 §5.15),这三处每次执行都可能吞掉别的在用 Socket 的 CONNECT/DISCONNECT 边沿位——即"丢中断"有一部分仍是**自家制造**,对账①②与 pump 的必要性里混入了这部分本可消除的噪声。
-建议:与驱动层审计(T2)合并处置:评估两级预清能否收窄为"只读本 Socket SINT"(B3 已证该做法成立且官方例程一律如此);若可行,对账①②的生产命中应同步下降(见 F-NET-8 的验证腿)。
-
-**F-NET-8 · TRAP-NET-17 对账不是 B3 固件 bug 的补丁——裸机台架已独立实证芯片丢中断,故 B3 修复后对账不可删;但命中数应可测得下降**
-证据:TRAP-NET-20 定案实验在**剥离 OS/net/FTP 的裸机芯片台架**上复现"握手完成但 CONNECT 中断被丢"(修复组 15/15,机制触发 5 次全救回);B3 修复在其后(4f2cc8b)。
-影响:直接回答本次审计的核心疑问——"根因已修的补丁是否仍必要":对 TRAP-NET-17 而言,其根因是芯片行为而非固件 bug,**必要**。
-建议:保留对账①②;补一条低成本台架验证腿:B3 修复前后各跑 `:21 100ms×50`,serial-assistant 统计 `reconcile:` 日志命中数,量化"自家制造的丢中断"占比,为 F-NET-7 收窄决策提供数据。
-
-**F-NET-9 · "close + 排空 RX + 排空中断 + 等 CLOSED"在 net 层有两套变体实现——下沉驱动层的最佳候选**
-证据:net_close_locked net_socket.c:2326-2345(close→循环 GET_SOCKET_STATUS + 读 SINT + 读全局→等 CLOSED,500ms 上限)与 net_relisten_locked 隔离段 net_listener.c:171-184(排空 RX→close→读 SINT→读全局→读状态→等 CLOSED/TCP_CLOSED + 最短等待)。两者语义同构(bit3=1 排空契约),参数不同(500ms vs 400/100/800ms),后者多排空 RX。
-影响:同一芯片契约两处实现,参数漂移风险(例如只有一处排 RX——close_locked 路径不排空 RX,依赖"close 前可读"契约由上层先排)。
-建议:裁决"下沉":在 CH395F 驱动层收口一个 `ch395f_close_flushed(sock, drain_rx, wait_ms)` 原语(具体签名归 T2 评估),net 层两处调用它并各自保留策略参数;BSD 层不再手拼命令序列。这是"让 BSD API 更主流"最实的一步。
-
-**F-NET-10 · retry_tick 的 L3 保险只挡"在途挂起 recv",不挡"ESTABLISHED 无挂起"——与 pump 的状态门不对称**
-证据:net_listener.c:400-412 重试循环无本层状态门(不看 state),仅 :405-408 跳过有挂起 recv 的 fd;对比 pump 明确只裁 LISTENING/CLOSED(net_listener.c:509-513),且 recv/accept/pending 三处即时重建都带内层 `state==CLOSED` 门(:1936/:2159 注释明言防 U12 同源事故)。
-影响:fd 在重试队列中且已因新 CONNECT 转 ESTABLISHED(无挂起 recv)时,到点重试会 close+open 摧毁新连接。可达性存疑(芯片不在听时新 SYN 难成,需 open 成/LISTEN 败的 U12 窗口配合),但不对称本身是隐患。
-建议:给 retry_tick 补与 pump 同型的状态门(仅 LISTENING/CLOSED 才重试),一行条件消掉整类竞态;补后 `net_pending_active` 保险可保留作第二道。
-
-**F-NET-11 · 注释漂移:handle_disconnect_event 写"pump 每 50 ms 回读",实际 100ms**
-证据:net_socket.c:3384("每 50 ms")vs net_listener.c:437(`NET_LISTENER_PUMP_PERIOD_MS 100U`,注释记载 50ms 曾把 uiTask 最坏单轮推到 1034ms 的实测)。
-影响:排障时按 50ms 预期判读日志时序会错判。
-建议:改注释;顺带该 LISTENING 分支(:3380-3399)现已纯留痕不动作,其 GET_SOCKET_STATUS 回读可裁决"简化"(pump 的 `chip not listening` 日志已含同等信息),删分支则少一条 SPI 与一处重复判读口径——低优先级。
-
-**F-NET-12 · "RX 非空但 readable=0"无人对账(U15),兜底被推给上层的低频盲收——三层兜底中最隐蔽的补丁**
-证据:net_can_read 注释自认"readable 仅由 RECV_OK 置位,TRAP-NET-17 对账不含 RX 非空但 readable=0 → 中断丢失时恒返 0,调用方须保留低频兜底 recv"(net_socket.c:1500-1501);ui_net_sink.c:552-558 与 iec104_serv.c:246-253 各自实现"每 32 轮无条件收一次";`net_listener_armed` 又用 `readable` 代理"芯片 RX 可能残留"(:1482-1485)。Trap 记录 B6/B10 一带将 U15 登记为未修残余。
-影响:数据面丢中断的兜底散落在两个应用任务里,net 层无对账——与"自愈内聚进 net/驱动层"的目标方向相反。
-建议:中期把"GET_RECV_LEN>0 但 readable=0"并入 pump 或独立低频对账(每 fd 一条命令,成本与 pump 同量级),随后上层 32 轮兜底可退役;短期保留现状但在两处上层注释里指向同一编号(U15),避免第三处复制。
-
-**F-NET-13 · time_sync 每会话 close 监听 fd 全量重建,违反自家铁律,是应用层重建所有者的最大残留**
-证据:time_sync.c:182 `net_close(conn_sock)`(单连接下 conn==listen)后外层循环 socket+bind+listen 重建(:224);设计文档第 7.4 节契约表已点名"违反『EOF 不得 close 监听 fd』→ close→重建沉降窗(TRAP-NET-09)+ 最坏 ~10s 端口下线"。对照正面样板:lftpd 控制通道明确不 close(lftpd.c:746-750/826-827)、iec104 EOF 分支带 `s_client != s_listen` 护栏(iec104_serv.c:259-262)。
-影响::8888 每次会话结束都主动制造一次 TRAP-NET-09 沉降窗;也是"应用层还能毁监听器"的活例。
-建议:裁决"简化(上层)":time_sync 会话收尾不 close,直接回外层在同一 fd 上 accept(lftpd 模式);iec104 `s_drop_conn` 的 close 属协议违例强制断连,语义正当,保留但注释标注为"有意例外"。
-**✅ 已实施(阶段 1,`b8c6a3d`)**:`net_close(conn_sock)` 删除,`ls` 提到外层循环跨会话常驻(仅网络代次变化/被毁时重建),`conn != ls`(模拟器多连接后端)才关数据 fd。台架实测连续 6 轮推送正常(周期 ~4s),每轮走 `standalone accept → DISCONNECTED, releasing → settle → auto-relisten ok`,零 `[ERR]`。契约变化已同步 `test/host/time_sync_server.py`(板子不再先关,PC 侧 2s 宽限后自关);⚠ 强杀 PC 进程会走芯片 `[ERR] TIMEOUT` + QUARANTINE 沉降(异常收尾,非正常路径)。
-
-**F-NET-14 · D10 的"2s 放弃"不是终态,`relisten give up` 日志误导排障**
-证据:net_listener.c:414-419 到期清队列 + DBG_ERROR;但 pump 在冷却后看到"芯片不在听"会再次 `net_listener_try`(net_listener.c:538-552),监听器实际会无限期重试(每 ~1.2s 一轮)。
-影响:日志出现 give up 时端口并非永久死亡,排障者可能误判严重度;同时"无限重试"缺少上限留痕(每轮一条 pump 日志)。
-建议:give up 文案改为"重试预算耗尽,交 pump 周期接管";或把 pump 的重触发也纳入同一预算计数(二选一,倾向改文案——保留最终兜底)。
-**✅ 已实施(阶段 0,`4c0a0af`)**:文案改为 `relisten retry budget exhausted, pump takes over`;同批补 0-4 retry_tick 状态门(仅 LISTENING/CLOSED/BOUND 之外的状态清陈旧登记,防 open→close 摧毁活连接)。
-
-### 1.4 重叠/兜底关系速览(谁兜谁)
-
-- 监听器重建:pump(周期唯一)→ 失败落 D10(30ms×2s)→ D10 放弃后 pump 冷却再触发(无限);消费者三处即时重建把 CLOSED 窗口压到 ~0(防上层 RPC 空转)。
-- 丢 CONNECT:探针(accept 内联,快)↔ 对账②(周期,兜底网);两者共享幽灵甄别;:2404 因门控过期只剩②(F-NET-4)。
-- 丢 DISCONNECT/静默回收:对账①(芯片 CLOSED 形态)↔ 对账⑤(芯片回 LISTEN 形态,阶段 1 起自动认领 = F-NET-2 已闭环)。
-- 会话结束感知:session_dead 电平(P1)兜住"抢先 relisten 吞 EOF";pending_tick ③ 与 recv DONTWAIT 双路径给 EOF;看门狗兜"完成逻辑缺位"。
-- 芯片收尾残留:TRAP-NET-18 沉降/隔离(重建前)↔ close_locked 排空循环(显式关闭)↔ listen/relisten 成功尾读本 Socket SINT——三处清理纪律,两套命令变体(F-NET-9 下沉候选)。
-- 数据面丢中断:RECV_OK 电平重放(TRAP-NET-08)+ readable 门控 + 上层 32 轮盲收(F-NET-12,唯一未内聚的一层)。
-
-### 1.5 裁决汇总
-
-| 机制 | 裁决 | 一句话依据 |
-|---|---|---|
-| auto_relisten / session_dead / released_abnormal / 幽灵甄别 / closed-with-data / 探针 / 对账①② / pump / D10 / TRAP-NET-18 沉降隔离 / L3 pending + 看门狗 | **保留** | 根因是芯片行为或已实测裁决的架构解;TRAP-NET-20 裸机台架是"不可从根上消除"的判据 |
-| net_relisten | **可删** | 生产零调用 + 对健康监听器是 footgun |
-| net_resync_listen | **可删**(先并入对账①) | 语义正确但载体错位:应是自动对账,不是手动 API |
-| net_can_accept | **可删**(先迁 iec104 到 armed 门控) | 过期门控致 :2404 探针失效 |
-| handle_disconnect_event LISTENING 分支回读 | **简化**(可选) | 已纯留痕,pump 日志可替代 |
-| net_listen_locked 3×500ms / net_close_locked 500ms 忙等 | **简化**(P2b,暂缓有据) | 与 D10 策略统一;等 task stall 因果证据 |
-| U12 LISTEN 失败不 close | **修复**(小) | 一行对称补齐 |
-| retry_tick 状态门 | **修复**(小) | 与 pump 对称,一行条件 |
-| 三处全局读清残留 | **下沉/收窄**(与 T2 合并) | B3 已证单 Socket 收窄可行 |
-| close+排空+等 CLOSED 命令序列 | **下沉驱动层**(与 T2 合并) | 两套变体一套契约 |
-| 上层 32 轮盲收兜底 | **下沉 net 层**(中期) | U15 并入对账后上层退役 |
-| time_sync 会话收尾 close | **简化(上层)** | 改复用同 fd accept(lftpd 样板) |
-| GRACEFUL 沉降门 `recv_bytes≠0` | **修正(一行级,见 F-NET-15)** | E1 背靠背失效循环的放大器;门的原保护目标已被 N-32 pump 延迟化架空 |
-
-### F-NET-15 背靠背数据连接失效循环(E1 发现 B 代码级根因,T5)
-
-失败签名(完整时间线见 §6 的 E1 结果一节):`n30_repro` 背靠背 STOR 4/50、复跑 1/10 成功,其余 425;sock4(FTP 数据监听)上数据连接 accept 后 ~20ms 被上一条连接的陈旧 DISCONNECT 判死(`DISCONNECTED, releasing (chip st=0x05/0x00)`),被杀会话 `recv_bytes=0` → 不登记 GRACEFUL → 立即 relisten → 下一条连接继续被杀(自持循环);closed-with-data accept 偶发救回 2 次但被 lftpd 镜像门控错过。
-
-#### Q1 · D20 陈旧甄别为何在芯片 TCP=0x00 时放行
-
-判据本体:net_socket.c:3404-3415(回读 :3408,判别 :3410)—— 单一规则:芯片 TCP==0x04(ESTABLISHED)判陈旧丢弃,**否则(含 CLOSE_WAIT/CLOSED)一律按真断开**;CLOSED 是刻意放进"真"集合的(注释 :3403「真断开时芯片处于 CLOSE_WAIT(0x07)/CLOSED(0x00) 等状态照常处理」)。
-
-放行的根因是**判据的证据通道被事件自身污染**:
-
-1. 滞后 DISCONNECT 在**拆链收尾完成时刻**锁存(TRAP-NET-12:DISCONNECT 在 FIN/ACK 完成后才上报),而拆链收尾完成的标志正是把 TCP_CLOSED(0x00) 写进该 Socket 的状态寄存器。事件投递与寄存器写 0x00 是同一时刻的两个面——甄别读到的 0x00 无法区分「真断开」与「陈旧事件 + 新连接仍活着」。
-2. 每 Socket 只有一个状态槽,存的是**最后一次状态迁移**(TRAP-NET-20 已证该寄存器在拆链窗口内双向不可信:既会假报 ESTABLISHED 幽灵,也会假报 CLOSED——新连接的 0x04 迁移被旧连接的 0x00 迁移覆盖)。E1 签名 `st=0x05/0x00` 即:Socket 仍 OPEN(0x05,被杀连接在芯片侧还活着),TCP 槽显示旧拆链的 0x00。
-3. **事件无连接身份**:芯片事件位不带连接 ID;幽灵甄别那套对端端口比对(:597-604、:1249)在此不可用——新握手已把芯片对端寄存器覆写为新端口,两种假设下都读到 `peer == remote_port(新) ≠ last_peer_port(旧)`,无判别力。
-4. 投递时序吻合:net_poll 同一次 SINT 读(:469)内 CONNECT 先于 DISCONNECT 派发(:480-489);`handle_connect_event` 在 accept 尾部读清 SINT(:3314-3316),所以致死位必须在 accept **之后**锁存——正是拆链收尾完成时刻(10~20ms,TRAP-NET-18 二阶段与 TRAP-NET-20 现象 3 均有记载)。新连接自己的握手 ACK 流量往往正是完成旧拆链最后一次 FIN/ACK 交换的那笔流量 → 背靠背强相关。
-
-结论:D20 在该时刻**结构性失明**——它的规则是在 TRAP-NET-16 时代标定的(当时事件先于寄存器写投递,芯片显示 0x04 可辨)。剩余可用判别轴只有两条:时间上的电平确认(对账去抖语义)或"年轻连接+零数据"启发式。
-
-#### Q2 · GRACEFUL 沉降门 `recv_bytes≠0` 对失效循环为何失效
-
-门本体:net_socket.c:3424 `recv_bytes != 0U`(外层已是 ESTABLISHED 状态门 :3400)才登记 GRACEFUL(:3430);不登记则下一次 relisten 跳过整个隔离段(net_listener.c:149 以 `quarantine != 0` 为门)。
-
-1. **受害者类别由事件时序定义,与数据无关**:连接在 accept 后 ~20ms 被杀,MCU 尚未读任何字节 → `recv_bytes==0` 是构造性必然(即便客户端已把 STOR 数据灌进芯片 RX——门统计的是 MCU 已读字节,不是芯片 RX 存量)。门按"收过数据"划危险类别,恰好把每一任受害者排除在外。
-2. **循环自持的关键:每一轮都重新制造残渣**。被杀连接在芯片侧仍 OPEN 活着(st[0]=0x05);下一次 relisten 的 open 返 USE → 失败分支 close(net_listener.c:214-222)→ 在芯片侧摧毁该连接 → 新拆链 → TRAP-NET-12 锁存新的滞后 DISCONNECT → 下一条连接再被杀。最初的残渣只是种子,循环靠"杀→close→拆链→滞后事件"自我繁殖。
-3. **为什么登记沉降就能断环**:登记后 relisten 走隔离段——提前 close 芯片 Socket(:174)、每 30ms 重试轮读清 SINT(:176)、≥100ms 保持期满才 open+listen(:182-197)。滞后 DISCONNECT 在"芯片 Socket 已关、本层无会话"的窗口内被消费,无害(对照:落在 LISTENING 上也只是被动留痕,net_socket.c:3380-3399)。
-4. **门的原保护目标已被架空**:注释 :3420-3422 写明门是为保住 TRAP-NET-17 的 `:21 @0ms×50` 快速复连时序而设(零数据连接免 100ms 沉降)。但 E1 发现 A 实测:N-32 P2a 之后**每次**会话收尾都要等 pump(115–900ms)+ D10,恢复窗 0.3–1s,`:7003` 复连 p50=1020ms(SYN 静默丢 → 客户端 RTO)。门如今省下的 100ms 落在 0.3–1s 的窗里不可见,换来的却是 P0 失效循环。
-5. 对照佐证:TIMEOUT 路径的 QUARANTINE **无条件**登记(net_socket.c:3517-3524)——所以 TRAP-NET-18 的 RST 中止修复从未循环过;循环是 DISCONNECT 有门路径独有的。
-
-#### Q3 · closed-with-data 救回后 lftpd 镜像门控为何错过
-
-门本体:lftpd_inet.c:116-120 —— 每次 accept 失败后 ftpTask **直读** `p_sock->state`(跨任务裸镜像,F-NET-12 家族),非 LISTENING 立即 `return -1` → 425(预算 5s,lftpd.c:369/401/532/599)。
-
-1. 每轮 `net_accept` 内部先走 CLOSED 分支(net_socket.c:1316-1328):RX 有数据 → closed-with-data 救回(返 fd);否则即时 relisten。所以镜像检查看到的是 accept 之后的状态:relisten 成功 → LISTENING(继续轮询);**relisten 失败 → 仍 CLOSED → 立即中止**。
-2. 而 relisten 失败恰恰集中发生在芯片拆链忙期(open 返 USE/BUSY)——镜像门在**恢复正在进行、剩余预算最值钱**的时刻中止,把最多 ~4.9s 预算直接扔掉。N-32 不变量 II 已把 `CLOSED + auto_relisten` 定义为"监听器仍在服役"(`net_listener_armed`,net_socket.c:1459-1489),lftpd 数据门控早于该语义、未迁移。
-3. 救回窗口本身很窄:被杀 → pump/D10/accept 轮在 ~30–130ms 内 relisten,其 close 顺带清掉芯片 RX;lftpd 100ms 轮询通常落在 RX 已清之后 → 救回输给 relisten(E1:46 败仅 2 救)。
-
-#### 因果链(一轮迭代,全部落行号)
-
-1. conn N accept:CONNECT → ESTABLISHED,SINT 预清(net_socket.c:3314-3316)。
-2. 旧拆链于 10~20ms 后完成(常由 conn N 自身 ACK 流量促成):状态寄存器写 0x00 + 锁存 DISCONNECT。
-3. net_poll 派发 → D20 读 0x05/0x00 → 判真断开 → CLOSED/session_dead/last_peer_port(:3434-3444);`recv_bytes==0` → 不登记 GRACEFUL(:3424)。
-4. relisten:open 返 USE(Socket 仍 OPEN 且 conn N 活着)→ close(net_listener.c:218)→ conn N 被芯片侧摧毁 → 拆链 N 滞后锁存;30ms 后 D10 重试 open+listen 成功(:262 成功尾 SINT 只清"已锁存"位)。
-5. 客户端下一条命令重连 → CONNECT → ESTABLISHED;拆链 N 完成 → DISCONNECT N → 回到第 2 步。
-6. 期间 lftpd accept_timeout 轮询撞上"relisten 失败的 CLOSED" → 镜像门中止 → 425。
-
-#### 最小修复候选(按侵入度递增)
-
-- **M1(一行级,net 层,首选先手)**:net_socket.c:3424 删去 `recv_bytes != 0U` 门(使 :3430 的 GRACEFUL 登记无条件化,外层 ESTABLISHED 门 :3400 保留)。断环位置=因果链第 3 步:登记后第 4 步自动走隔离段(提前 close + 逐轮 SINT 排空 + ≥100ms 保持),下一条连接出生干净。代价:每次会话收尾恢复窗 +100ms——相对实测 0.3–1s 的 pump 主导窗为边际量。必跑回归:`:21 @0ms×50`(门当初的守护用例)、`ftp_regression` 11/11、`ftp_abort_probe` RST/FIN、`n30_repro --n 50`。
-- **M2(一行级,应用层)**:lftpd_inet.c:117 改判 `net_listener_armed(socket) == 0`(模拟器双端已有实现)。让 5s 预算穿过"恢复中的 CLOSED"窗口,救回/pump 重建得以在窗内落地;同时消灭一处裸镜像。注意 M2 单独不杀循环(连接仍被杀),只把"立即 425"换成"预算内恢复";与 M1 合用后循环死亡,M2 退化为时延/健壮性修正。
-- **M3(结构性,阶段 1)**:D20 年轻连接电平确认——对"刚转 ESTABLISHED 且零数据"的连接上的 DISCONNECT 不吃边沿,交给对账①电平确认(200ms×2 去抖):连接真活则下轮读到 0x04 存活;真断则 ~400ms 内由对账①释放。需一个"年轻"标记(复用 `lost_conn_seen` 或加 accept 时刻字段)。直击"杀"本身,改动面大于 M1,建议 M1 台架数据出来后再做。
-- **M4(供给面,即 F-NET-7)**:三处全局读清(net_socket.c:1668-1670、:2332-2334,net_listener.c:176-178)收窄为单 Socket SINT 纪律。全局位被丢弃式读清 + SINT 粘滞 = **搁浅**:滞后 DISCONNECT 可被推迟到任意未来连接上投递,把有界的 10~20ms 危险窗变成无界。§6 E1 的 G2 归因即此。M1 不依赖 M4 生效,但 M4 缩小危险供给面。
-- **M5(结构性,即 F-NET-9)**:`sock_reset` 复合原语(close+排空 RX+排空中断+沉降+relisten 收进一个驱动层事务)让沉降**无条件且内联**,同时消掉发现 A 的 pump 延迟窗(`:7003` p50 1020ms 的根源)——M1 的 +100ms 代价在该形态下归零。
-
-#### 与此前发现的关系
-
-- **F-NET-7**:全局读清是本循环的**供给放大器**(把滞后事件从"有界 10~20ms"放大为"无界搁浅");M1 是需求侧止血,M4 是供给侧收口,两者正交。
-- **F-NET-9**:M1 是在现有"两套沉降变体"之上打的一行补丁;F-NET-9 的复合原语是同一方向的深水区——"沉降无条件化"在事务化后不再需要门来平衡时延。一行级修正与下沉原语不矛盾,是同一裁决的先后两步。
-- **F-NET-12**:lftpd 镜像门控是"上层直读 net 状态镜像"的第一例**实测 P0 代价**,为 F-NET-4 的 armed 门控迁移提供了数据支撑。
-- **F-NET-11** 结论仍立:落在 LISTENING 上的滞后 DISCONNECT 无害(被动留痕);循环需要它落在 ESTABLISHED 上,即投递必须晚于 accept——这正是拆链完成时刻的投递形态。
-
-#### 回归集建议
-
-把 `n30_repro`(背靠背 STOR)纳入常备回归集——TRAP-NET-20 终版回归只含有间隔的 `ftp_regression`,背靠背形态不在集内,这是本次 9/10 能潜伏的测试面原因(§6 对照台账已登记)。验收线建议:`n30_repro --n 50` 通过数与 `:21 @0ms×50`、`ftp_regression`、`ftp_abort_probe` 同时全绿,由台架定具体门槛。
-
-### F-NET-17 残余偶发的真身:风暴态(E8 深查,2026-10-13)
-
-E7b 的"残余 ~3%"不是稳定值:同固件同负载,故障率随设备状态在 **2%(全新启动)~ 54%(残渣累积)~ 12%(仅复位)** 间摆动(§6 E8 矩阵)。"偶发"的真身是**可自持的风暴态**,三个缺陷叠加:
-
-1. **沉降自再生锁存**(M1 的硬币):沉降段 `net_listener.c:174` close 之后,拆链收尾在 ~10-20ms 后(TRAP-NET-12 延迟锁存)**重新置位** DISCONNECT;`:176` 的 SINT 读清与 close 同轮发生,读不清这枚迟到的锁存 → 沉降最后一轮"清干净"的假象下,relisten 完成后 11ms 首条连接被杀。30ms 重试粒度 vs 10-20ms 锁存延迟 = 每次重建一次硬币——E7b 的 2% 即此。
-2. **泵与救回互抢**(风暴燃料):沉降段 `:172` `net_drain_chip_rx` 排空的正是 closed-with-data 救回(net_socket.c:1317)依赖的 RX 数据;救回 accept 成功返回 fd(:1352)但会话随即被杀/数据被排空 → lftpd 短读 → 425。E8 窗口 31 次 accept timeout 与 38 次救回并存为直接证据。
-3. **芯片 OPEN 卡死放大**(自持结构):被杀会话芯片侧仍 OPEN(0x05),新 SYN 被接进卡死 socket(TCP 槽 0x00)→ accept → 下一枚锁存再杀 → 直到 800ms 强制放行 + close 真正落拆链才出簇。风暴期时间线(0.75s 内 3 轮"救回→杀→救回")见 §6 E8 节。
-
-**设计修正(覆盖 T5 版 M3/M5 表述)**:
-- **M3 判据**:`chip=0x05/0x00`(Socket OPEN + TCP 槽 0x00)+ 连接年轻(<1s)= 逻辑矛盾态(活连接应 0x04)→ 不吃边沿,交对账①电平去抖二次确认。风暴窗口 100% 命中、健康路径(0x00/0x00 或 0x07)零误伤。
-- **M5 语义**:`sock_reset` 必须 **drain-until-quiescent**(close 后轮询 SINT 直到 DISCONNECT 锁存出现并读清、连续两轮无新锁存才 open+listen);固定时间窗在锁存延迟抖动下必输硬币。
-- **新红线**:RX 有数据 = 消费者资产——沉降/pump 重建前必须让位 accept 救回路径,排空后才准 reset;救回与重建必须单一所有者(`sock_reset` 内联裁决)。
-
-### F-NET-17 补充:风暴态三机制代码级核实与 M3/M5 设计(T6,2026-10-13)
-
-> 行号 = 提交 9ac9879(M1+M2+M4-lite)后的实测行号。只读核实,未改产品代码。
-
-#### T6-① 「close 已 CLOSED 的芯片 Socket 产生 DISCONNECT 锁存」——基石**修正**而非照单确认
-
-**手册口径**:§5.35「关闭 Socket 前 CH395 会自动断开 TCP 连接」(Trap 记录定案更正引用)——close 的动作序是"先断链再关 Socket";若 TCP 已 CLOSED,断链环节无事可做。§5.52② bit3=1 只豁免 DISCONNECT/TIMEOUT 场景的自动关闭/清缓冲,**不豁免 CLOSE_SOCKET 命令本身**(close 仍清缓冲——net_socket.c:1311 注释"close+open 清缓冲"同此)。SINT bit4=DISCONNECT(ch395f.h:268)。
-
-**证伪强形式(干净情形)**:TC-NET-204(TRAP-NET-07 定案更正,Trap 记录实测表)模式 C 正是本问题的受控实验——DISCONNECT 已发生、芯片停在 `0x05/0x00`(与风暴签名逐位相同),补 `close` → **+0 ms 回 CLOSED**、重开重连 5/5 OK;且"15 轮重开均未观察到残留 DISCONNECT(0x10)——close 顺带清掉了锁存"。即:**close 一个已 CLOSED 的 Socket 不产生新锁存,反而顺带清掉已锁存的位**。模式 A(close 一条活连接,主动拆链)5 轮同样无残留锁存。
-
-**与 TRAP-NET-12 的张力消解**:TRAP-NET-12 的残留锁存不是 close 命令制造的,而是**拆链完成事件**——"DISCONNECT 可能在与 CLOSED 状态达成几乎同时、甚至之后才锁存"(Trap 记录 TRAP-NET-12 根因节),粘滞跨会话(B3 条目"SINT 粘滞到被读")。锁存的生成者是拆链过程(多为对端 FIN 的被动拆链收尾),不是 close 命令。
-
-**风暴窗口的直接反证**:E8 时间线里 kill 发生在 `auto-relisten ok` **之前**(窗口内 relisten 尚未完成,沉降段 close 还没跑或没再跑)——那几枚 11ms 杀连接锁存**不可能由沉降段 close 产生**。真实来源两条:(a) 上一条 STOR 的客户端 FIN(FTP 正常行为,发完即关,TRAP-NET-20 定案)的被动拆链收尾迟到完成(TRAP-NET-12/-18 的 10~20ms,TRAP-NET-20 的 >2s 不可观测窗);(b) 卡死 socket(0x05/0x00)把新 SYN 接进来又自行拆掉(`docs/芯片手册/CH395F.md` §5.29 语义域),每轮自产一对 CONNECT/DISCONNECT。
-
-**但"沉降末轮 SINT 读清不能保证静默"的实操结论仍成立**,经两条路径:
-1. 拆链完成滞后于末轮读清(lead 说的硬币——生成者是拆链,不是 close);
-2. **搁浅**:剩余两处全局读清(net_socket.c:1670 `net_connect_locked`、:2334 `net_close_locked` 等待环)焊死 net_poll 全局门(:441→:464)→ 锁存被搁到下一条连接的 CONNECT 全局位被读到那一刻,net_poll 同轮读 SINT 见 CONNECT|DISCONNECT 双置 → 先 CONNECT(:480-483)后 DISCONNECT(:486-489)→ accept 后毫秒级被杀。这是"锁存跨越整个沉降窗落到下条连接"的投递机制(F-NET-7 残余的实测代价)。
-
-**基石判定**:现象成立、机制表述须修正——"close 已 CLOSED Socket 会再锁存"应改写为"**TCP 槽 0x00 不证明拆链已完成;拆链完成事件滞后且粘滞;close 命令本身对死 Socket 不产生锁存(TC-NET-204 模式 C),对活连接的主动拆链在该实验窗口内亦无残留(模式 A)**"。此修正直接改写 M5:静默判据必须是"**等到预期锁存出现并读清 + 连续静默轮**",而不是"close 后假设会生成锁存然后清掉";且静默可被新连接自身流量打破(无界窗)→ **M3 是结构性必需,不是可选加固**。
-
-#### T6-② 泵与救回抢同一 RX 缓冲——**证实**,排空后救回判据必然失败(对已存在数据)
-
-- 同一缓冲:`net_drain_chip_rx`(net_listener.c:102-121)经 GET_RECV_LEN+READ_RECV_BUF 消费(读即推进);closed-with-data 救回判据 net_socket.c:1317 `ch395f_get_recv_len(sockfd) > 0` 读同一寄存器源。两者都在 netTask 上下文(accept RPC 与 pump/relisten 串行)→ **不是数据竞态,是先后竞态**:谁先跑谁定生死。
-- 排空后 `recv_len==0` → :1317 判据对"排空时已存在的数据"**必然失败** → accept CLOSED 分支落到 :1327 即时 relisten(同样进沉降段)→ 资产二次销毁(close 清缓冲)。唯一例外:引擎仍活、客户端续发,可在两次排空间隙回填 RX——但沉降段每 30ms 排一次(:172 每重试轮)、lftpd 100ms 轮询一次 → 泵压倒性赢。
-- E8 佐证:风暴窗 31 次 accept timeout 与 38 次救回并存 = 竞态双峰的直接证据(赢一次的救回拿到的也常是被排空半份的数据 → 短读 425)。
-- 结论:lead 的新红线成立。裁决点收进 sock_reset 第一步(见 T6-④ QUIESCE 第 1 步):RX>0 且芯片 st[0]==OPEN 且本层 CLOSED+auto_relisten → **让位 accept**(返 BUSY 不排空),RX==0 或强制放行后才准排空+reset。
-
-#### T6-③ M3 最小补丁设计(一行级判据 + 一行级前置清零)
-
-**字段选型:复用 `lost_conn_seen`(net_types.h:190)作 ESTABLISHED 期"可疑电平"(0/1),零 RAM**。
-- 生命周期兼容:探针只在 LISTENING 期跑(accept 门 :1298),与 ESTABLISHED 期不重叠;relisten(net_listener.c:249)/pump③(:538)/listen(:1121)/RESYNC(:792)已有清零点。
-- **前置一行(必须)**:`handle_connect_event` 现**不清** lost_conn_seen(grep 实测:清零点无此函数)——LISTENING 期探针残留的计数会带进新连接,使可疑判据首轮即被旁路、M3 失效。须在两个转 ESTABLISHED 分支(:3306/:3344 附近)补 `p_sock->lost_conn_seen = 0U;`。
-- 备选(新增 `uint32_t estab_ms` 支持"<1s 年轻窗",32B/8 sock)不推荐:矛盾判据本身即判别器,flag 比时钟更准——滞后锁存同样能杀老年连接,age 窗会漏;代价仅为真 RST 释放也延 ~400ms(可接受,见误伤分析)。
-
-**判据插入点**:`handle_disconnect_event` ESTABLISHED 分支,D20 甄别(:3410-3415)之后、释放留痕(:3418)之前——`st[]` 已在 :3408 读过,**零额外 SPI**:
-
-```c
-if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) &&
- (p_sock->lost_conn_seen == 0U)) {
- p_sock->lost_conn_seen = 1U;
- DBG_WARN("sock%d suspect DISCONNECT on live conn (chip 0x05/0x00), defer to reconcile", sockfd);
- return;
-}
-```
-
-**交对账①的衔接语义——现有代码可直接认领,对账零改动**:对账①判据(:588-589)`ESTABLISHED && (st[0]==CLOSED || st[1]==CLOSED)`,0x05/0x00 经 `st[1]==CLOSED` 命中;去抖 `s_bad` 连续 2 轮(200ms×2≈400ms)后复用 `handle_disconnect_event`(:633)再入——此时 `lost_conn_seen==1` 旁路可疑判据 → 正常释放(GRACEFUL 登记、session_dead、CLOSED、回调全套不变)。双锁存场景(第二枚边沿到来)同样因 flag 已置而直接释放:双锁存=强证据,不再三确认。
-
-**去抖期间的状态摆位——保持 ESTABLISHED、session_dead 保持 0,这正是收益所在**:
-1. lftpd accept 轮询见 ESTABLISHED → :1305 门通过 → **accept 正常返 fd**(不再依赖 closed-with-data 救回);
-2. recv 快路径无状态门槛 → 消费者在 400ms 租约内排空 RX(引擎活则数据续到);
-3. 对 n30 的小段 STOR:400ms 租约足以让传输完成 + 客户端正常 FIN(0x07 健康释放)→ **风暴链被"会话正常完成"掐断**;
-4. 槽恢复 0x04 的形态(TRAP-NET-16 时代可恢复类):对账① bad=0 → 连接存活,flag 由 relisten/connect 清;
-5. 引擎真死:消费者排空后 WOULDBLOCK,对账①释放后得 EOF——EOF 晚 ~400ms,语义不变。
-
-**:21 @0ms 快复连为何零误伤**:
-1. 健康 FIN 释放:芯片处 CLOSE_WAIT(0x07)(bit3=1 芯片不关 Socket,CLOSE_WAIT 等 MCU 关——D20 注释 :3403),判据要求恰 `0x05/0x00`,0x07 不命中;
-2. 芯片静默回收形态 `0x00/0x00`:判据要求 st[0]==0x05,不命中;
-3. 真 RST(恰 0x05/0x00)会命中可疑 → 代价仅释放延 ~400ms(对账①两轮),不是错状态;且 @0ms 复连时序已被 pump 窗主导(E7b `:7003` p50=1011ms),400ms 落在窗内不可见;
-4. E5 健康 FIN 20/20 样本走 ①②的形态,均在可疑集合之外。
-
-**成本**:判据 4 行 + connect 清零 1 行 + 1 条 DBG(~90B Flash),零 RAM。
-
-#### T6-④ M5 `sock_reset` drain-until-quiescent 状态机草案(阶段 2)
-
-替换现三数组 `s_relisten_quarantine/_settle_start/_settle_dead`(net_listener.c:150-208)为 per-socket 相位机;五个重建入口(pump/D10/accept CLOSED 分支/recv EOF/pending③)已全部收敛到 `net_listener_try`,状态机天然单所有者。
-
-```
-IDLE ──(settle 登记或 try 进入)──▶ CLOSE
- CLOSE: 发一次 CLOSE_SOCKET(TC-NET-204:顺带清已锁存位,保留此动作);t0=now;latch_seen=0;quiet=0
-QUIESCE(每 30ms 重试轮,非阻塞):
- 1. RX 裁决(红线): GET_RECV_LEN>0 且芯片 st[0]==OPEN 且本层 CLOSED+auto_relisten
- → 让位 accept:返 BUSY,本轮不排空不 close
- 否则 → net_drain_chip_rx 排空
- 2. GET_INT_STATUS_SN 读即清: 见 DISCONNECT 位 → latch_seen=1, quiet=0;
- 否则若 latch_seen → quiet++
- 3. GET_SOCKET_STATUS: 记 st
- 4. 静默成立 := st[0]==CLOSED
- && (latch_seen || now-t0 >= LATCH_GRACE_MS) // 等过锁存延迟窗
- && quiet >= QUIET_ROUNDS // 连续两轮无新锁存
- && RX==0
- && now-t0 >= hold_min // GRACEFUL 100 / QUARANTINE 400(沿用)
- → OPEN
- 5. now-t0 >= RESET_MAX_MS → 强制放行 OPEN(留痕,沿用现兜底)
-OPEN: SET_PROTO_TYPE/SET_SOUR_PORT/OPEN/TCP_LISTEN + 成功尾(现 :207-267 不动);
- 失败 → D10 30ms 重试,每轮先回 QUIESCE 第 2/3 步轻量复核(1 条 SINT + 1 条状态)
-```
-
-- 参数建议:`LATCH_GRACE_MS=50`(实测锁存延迟 10~20ms,×2.5 余量)、`QUIET_ROUNDS=2`(2×30ms)、`RESET_MAX_MS=800`(保持"必须 < 2000 放弃时限"规则)、`hold_min` 沿用两级。
-- **诚实边界(来自 T6-① 修正)**:静默不是有界保证——拆链完成可被**新连接自身流量**触发(TRAP-NET-20 窗口 >2s 不可观测),状态机只能静默"已在途的拆链";"未来流量触发的拆链"只有 M3 的电平确认能接住。**M5 与 M3 互补成对,不是替代关系**:M5 消灭"沉降窗内可静默"的绝大多数锁存(E7b 的 2% 硬币),M3 兜住无界残余(风暴态)。
-- RAM:三数组(uint8 + 2×uint32 ≈ 9B/sock)→ 相位 uint8 + t0 uint32 + 打包 flags ≈ 6B/sock,微负,预算友好。
-- 与 T6-② 的闭环:QUIESCE 第 1 步即红线裁决点——"泵与救回互抢"在状态机内单点裁决,五入口不再各自竞速。
-
-
----
-
-## 2. CH395F 驱动层审计(T2)
-
-> 审计对象:`Drivers/BSP/CH395F/ch395f.c`(1411 行)/ `ch395f.h`(750 行)、`Src/` 中断与引脚配置、
-> `Drivers/BSP/NET/net_socket.c`(3671 行)与 `net_listener.c`(554 行)中**由驱动层契约决定的部分**。
-> 方法:驱动 API 全量清点 + 中断路径实证 + 全部 Trap 台账通读归类 + git 考古定位根因修复落点。
-> 行号均为本工作树 HEAD(`09d6195`,分支 `task/N34-bsd-selfheal-audit`)实测行号。纯只读,未改任何代码。
-> 参照文档:CH395F_Trap_Records.md(含 2026-10-11 寄存器审计 B1~B10)、
-> 网络Socket监听生命周期设计说明.md、BSD_Socket_API_使用指南.md、§3(主流对照,T3)、
-> §1(net 层盘点,T1)。
-
-### 2.1 驱动层 API 面盘点
-
-#### 2.1.1 驱动"知道"什么(可回读的芯片事实)
-
-| 芯片事实 | 驱动 API | 位置 | 语义要点 |
-|---|---|---|---|
-| Socket 是否打开 + TCP 态 | `ch395f_get_socket_status` | ch395f.c:1356 | **2 字节**输出:`[0]`=`00H`CLOSED/`05H`OPEN,`[1]`=TCP 态;`[1]` 仅在 `[0]`=OPEN 时有意义 |
-| 单 Socket 事件 | `ch395f_get_sock_int_status` | ch395f.c:1324 | **读即清**(GET_INT_STATUS_SN) |
-| 全局事件汇聚(16 位) | `ch395f_get_glob_int_status_all` | ch395f.c:1305 | **整体读即清**,芯片无按位清除手段 |
-| RX 积压字节 | `ch395f_get_recv_len` | ch395f.c:1274 | 低字节在前;可反复查询(手册 5.34) |
-| 对端 IP:Port | `ch395f_get_remot_ipp` | ch395f.c:336 | 6 字节,**小端**(与 SET_DES_IP 的点分序相反) |
-| PHY/链路 | `ch395f_get_phy_status` | ch395f.c:875 | 返回 `01H`DISCONN…`08H`100M FULL |
-| 命令完成 | `ch395f_get_cmd_status` / `ch395f_poll_cmd_status` | ch395f.c:629 / :654 | 后者 `HAL_Delay(20)` **阻塞忙等** |
-| SPI 通道健康 | `ch395f_spi_byte_fault` | ch395f.c:265 | 锁存式(复位前不清),仅首条故障记日志 |
-| 芯片版本 | `ch395f_get_version` | ch395f.c:310 | 仅 net_init 打印一次(net_socket.c:259),**不参与门控** |
-| MAC/IP/掩码/网关 | `ch395f_get_mac_addr` / `ch395f_get_ip_inf` | ch395f.c:819 / :847 | 回读用于自检留痕 |
-| ARP 不可达现场 | `ch395f_get_unreach_info` | ch395f.c:1402 | **全仓零调用**(B4) |
-
-#### 2.1.2 驱动暴露的原语(51 个公共函数,按职责分组)
-
-| 组 | 函数 | 位置 |
-|---|---|---|
-| SPI 事务 | `write_cmd` / `write_data` / `read_data`(**公开**)、`spi_byte_fault`;`spi_begin`/`spi_end`/`spi_dma_transfer`/`spi_fault_set` 为 static | ch395f.c:212/229/246/265;:76/:87/:120/:100 |
-| 芯片检测与初始化 | `check_exist` / `get_version` / `reset` / `get_cmd_status` / `poll_cmd_status` / `init` | :278/:310/:613/:629/:654/:680 |
-| 网络参数 | `set_ip_addr` / `set_gwip_addr` / `set_mask_addr` / `set_mac_addr` / `get_mac_addr` / `get_ip_inf` / `set_phy` / `get_phy_status` / `ping_enable` / `set_dhcp` / `get_dhcp_status` / `set_arp` / `set_ttl` / `get_unreach_info` | :702/:731/:760/:791/:819/:847/:898/:875/:918/:937/:956/:1375/:1389/:1402 |
-| 缓冲几何 | `set_recv_buf` / `set_send_buf` / `clear_recv_buf` | :416/:443/:468 |
-| TCP 全局参数 | `set_fun_para` / `set_tcp_mss` / `set_retrans_count` / `set_retrans_period` / `set_keepalive_idle` / `_intvl` / `_cnt` / `_enable` | :372/:394/:573/:592/:487/:509/:531/:551 |
-| Socket 生命周期 | `set_proto_type` / `set_des_ip` / `set_des_port` / `set_sour_port` / `open_socket` / `close_socket` / `tcp_listen` / `tcp_connect` / `tcp_disconnect` | :980/:1003/:1034/:1059/:1085/:1100/:1115/:1130/:1145 |
-| 数据面 | `write_send_buf` / `read_recv_buf` / `get_recv_len` | :1166/:1222/:1274 |
-| 状态回读 | `get_glob_int_status_all` / `get_sock_int_status` / `get_socket_status` / `get_remot_ipp` | :1305/:1324/:1356/:336 |
-
-**驱动持有的状态 = 零。** 全文件仅 4 个 static(ch395f.c:50-56):`s_spi2_dma_done`、`s_spi_fault`、
-两个 DMA 缓冲。没有任何 socket 状态缓存、没有链路态缓存、没有事件缓存——驱动是纯命令转发层。
-
-#### 2.1.3 SPI 事务包裹与集中中断读取约定
-
-- 每次事务由 static 的 `ch395f_spi_begin()`(CS PB12 SET→RESET)/ `ch395f_spi_end()` 包裹(ch395f.c:76/:87),
- 但 `write_cmd/write_data/read_data` 是**公开**的(ch395f.h:301/:308)——任何调用方都能拼一条未包裹的事务。
-- SPI2 分频 **8**(Src/spi.c:77-88,CPOL LOW / CPHA 1EDGE / 软 NSS),PCB 约束,勿改。
-- 集中中断读取约定落在 **net 层**:`net_poll()` 先读 INT# 引脚(net_socket.c:435),低电平才读
- `GET_GLOB_INT_STATUS_ALL`(:441),再按 `1U<<(i+4)` 门控去读该 Socket 的 SINT(:464/:469)。
- 驱动只提供两个裸的读即清原语,**没有任何护栏**。
-
-#### 2.1.4 中断路径结论:纯轮询,无 ISR
-
-- `CH395_INT_Pin`(PE5)在 `Src/gpio.c:76-80` 配为 `GPIO_MODE_INPUT` + `GPIO_NOPULL` → **无 EXTI、无 ISR**。
-- `EXTI9_5_IRQn` 属于 `TP_BUSY_Pin`(PC7,Src/main.h:124-125、gpio.c:170-171、stm32f4xx_it.c:391),与 CH395F 无关。
-- 事件消费节奏 = netTask 主循环 `net_poll()` + `net_process_messages()`(App/task/net_task.c:241-279,约 1ms 一轮)。
-
-**判定:现状正确,不应改 EXTI。** 芯片的全局事件寄存器是"整体读即清",ISR 内读寄存器会把事件从
-netTask 手里抢走(正是 B3 事故的形状,见 F-DRV-2)。缺的不是中断,而是**驱动侧的事件分发与缓存**。
-
-### 2.2 Trap 根因归类总表
-
-台账共 **24 条**(旧编号 01–25,**旧 19 从未存在**,属悬空引用)。根因类别:
-**(a)** 芯片真实行为(不可在固件侧根治);**(b)** 固件寄存器值/常量错误;**(c)** 引用错误
-(字节序/偏移/索引/单位);**(d)** 时序误用(顺序/忙等/沉降);**(e)** 设计缺口;**(f)** 判读或台架假象。
-
-| Trap | 症状(一句话) | 类别 | 根因修复落点 | 催生自愈补丁 | 根因修好后补丁是否仍必要 |
-|---|---|---|---|---|---|
-| 01 | Socket 4~7 的 CONNECT 中断不触发 | (d)+(a) | net_socket.c:333-340(net_init 内 7→0 逆序给全部 8 个 Socket 显式配缓冲) | 否 | — |
-| 02 | Socket 4~7 发送固定垃圾/接收不落数据(缓冲重叠) | (c) | net_socket.c:334-340 + ch395f.h:136-141(固定 6 块/Socket,用满 48 块) | 否 | — |
-| 03 | DHCP 包 xid 偏移错 | (c) | 主机侧测试工具(历史) | 否 | — |
-| 04 | UDP 写完不等 SENBUF_FREE → 下次写被静默丢弃 | (a) | net_socket.c:2721-2753(`send_ready` 门控) | 否(属正常流控) | 是 |
-| 05 | 自动协商与 RTL8305NBI 不兼容 | (a) | net_task.c:110(强制 100M 全双工) | 否 | 是 |
-| 06 | KeepAlive 非 500ms 倍数 → 内部定时器异常 | (a) | net_config.h:61-71 + net_socket.c:288-290 | 否 | 是 |
-| 07 | 只 disconnect 不 close → 重开必返 `0x20` | (f)→(a) | 原判"FIN_WAIT_2/RST"已证伪;真因 `FUN_PARA` bit3=1 芯片不自动关 Socket。轮询 **byte0**:net_socket.c:2326-2345 | 间接(催生"close→等 CLOSED→排空"序列) | 是(bit3=1 契约) |
-| 08 | RECV 中断无限触发 | (a) | net_socket.c:429(`do{}while(0)` 单批处理) | 否 | 是 |
-| 09 | PHY link up 同一时刻 `open_socket` 失败 | (a)+(d) | 根因侧已修:就绪屏障 net_task.c:232 + 启动期等 PHY link net_task.c:118-130 | **是**(D10 层) | **部分**:启动形态已被根因消除;close→reopen 沉降仍是芯片行为(TRAP-NET-20 实证窗口不定长)→ 重试保留,**应用层重复重试可删** |
-| 10 | 6 个 API 永远返回 -1(消息队列按值拷贝) | (e) | 错误码改经 `xTaskNotify` 通知值回传 | 否 | — |
-| 11 | accept 拒绝 standalone_accept 的 ESTABLISHED | (e) | net_accept_locked(net_socket.c:1281-1342) | 否 | — |
-| 12 | FatFs `FR_INVALID_NAME` | 非 CH395F | lftpd_io.c | 否 | — |
-| 13 | 分批读后 `recv_len` 归零 | (f) 存疑 | 无(机理未证实,与手册 5.34 及本仓 TC-NET-201 相反) | 否 | **约束已事实作废**(见 F-DRV-11) |
-| 14 | close 后 DISCONNECT 滞后锁存,污染下次首读 | (a) | 预清 + 反核实:net_socket.c:2326-2345 / :1137、net_listener.c:262、net_socket.c:1670 | **是** | 是(TRAP-NET-18 实测伪 DISCONNECT 仍投递给下一条连接) |
-| 15 | DHCP 成功后 IP 寄存器提交滞后 | (a) | 主机侧判据修正(历史) | 否 | — |
-| 16 | TCP Client 目的 IP 字节序写反 → CONNECT TIMEOUT | (c) | net_socket.c:1636-1641 + :2691-2701(改大端),commit `db64e6ac` | 否 | 根因已修;**驱动 API 未封装字节序 → 复发面仍在**(F-DRV-5) |
-| 17 | client 重连被上一条连接的残留 DISCONNECT 秒断 | (a) | 预清 + 反核实:net_socket.c:1660-1726 | **是** | 是 |
-| 18 | "bit0=1 才是多 Socket 模式" | (f) 伪根因 | net_socket.c:270 回到 `0x08`(commit `d758d5c`);真正起作用的是 TRAP-NET-02 的缓冲分配 | 否 | 驱动侧残留:语义宏未被使用(F-DRV-6) |
-| 19 | — | — | **不存在**(悬空引用) | — | — |
-| 20 | 单次写 >1KB 写穿发送 FIFO → 数据错乱/卡死 | (c) | ch395f.h:138-141 + net_socket.c:1745/1788/2717(`NET_SEND_CHUNK_MAX`) | 否 | 根因已修;驱动只告警不拦截(F-DRV-7) |
-| 21 | 接收缓冲"恰好填满+排空"窗口冻结 | (f) 非缺陷 | 台架自吞数据 + 判读工具误判;保留金丝雀 ch395f.c:1176-1189 与布局解耦 ch395f.h:130-153 | 否 | — |
-| 22 | 快速复连丢 CONNECT/DISCONNECT → 监听静默死亡 | (a)(B3 曾自造同类症状,commit `4f2cc8b` 修) | net_reconcile_tick net_socket.c:548-641 | **是**(对账层) | **是**(TRAP-NET-20 裸机台架独立实证芯片丢中断);但命中数应重测 |
-| 23 | 上传中止后首笔复连被静默截断(≤2KB) | (a)+(e) | net_listener.c:102-121(排空)+ :149-204(隔离段);登记点 net_socket.c:3430/:3523 | **是**(隔离排渣层) | 是 |
-| 24 | MAC 写入顺序违反手册(SET_MAC 须在 INIT 前) | (d) | net_socket.c:292-299(commit `de1b50e`) | 否 | 已根治 |
-| 25 | FTP 数据连接三连:丢 CONNECT / accept 竞态自毁缓冲 / 背靠背小段毒化 TX | ①(a) ②(e) ③(a) | ①探针 net_lost_connect_probe net_socket.c:1228-1267 + 幽灵甄别 `last_peer_port`;②closed-with-data accept net_socket.c:1306-1334(**我方 bug,已根治**);③批量发送 lftpd.c:107-155 | **是** | ①③是;②属契约实现,保留 |
-
-**归类小结**(按定案后的主因计,跨类条目只计一次):(a) 芯片行为 12 条、(c) 引用/数值错误 4 条、
-(f) 判读假象 3 条、(d) 时序误用 2 条、(e) 设计缺口 2 条;另旧 12 非 CH395F、旧 19 不存在。
-
-**催生五层自愈补丁的 6 条(09/14/17/22/23/25)中,只有 TRAP-NET-09 的
-"启动形态"和 TRAP-NET-20 的"②accept 竞态"被根因修复消掉;其余全部落在芯片行为上,不可从根上消除。**
-
-### 2.3 五层自愈 × 驱动层契约
-
-| 自愈层 | 参数与位置 | 驱动层是否已有对应能力 | 结论 |
-|---|---|---|---|
-| `auto_relisten`(监听常驻) | net_types.h:172;置位 net_socket.c:1119 | 无——芯片永不自动回 LISTEN(单连接复用模型),必须软件承诺 | **保留**(模型基石) |
-| D10 非阻塞重监听重试 | net_listener.c:77-79(30ms/2s)、:394-432 | 驱动只有**阻塞**的 `poll_cmd_status`(ch395f.c:654)→ 非阻塞重试是在 net 层绕开驱动阻塞原语 | **保留**,但真正的解法是驱动提供非阻塞命令完成查询(F-DRV-8) |
-| TRAP-NET-17 周期对账 | net_socket.c:544-546(200ms/去抖 2/冷却 1s)、:548-641 | 驱动无状态缓存 → 对账只能每周期重新发 SPI | **保留**(根因是芯片丢中断);参数与 pump 重复,应收敛(F-DRV-1) |
-| N-32 监听器 pump | net_listener.c:437-441(100ms/去抖 2/冷却 1s)、:465-553 | 同上 | **保留**(不变量 II 的落点,不是补丁) |
-| TRAP-NET-18 排渣隔离 | net_listener.c:53-56(400/100/800ms)、:102-121、:149-204 | 排空 RX 的**机械**(`get_recv_len`+`read_recv_buf` 循环)住在 net 层;驱动只有裸 `clear_recv_buf`(且产品零调用) | **下沉驱动**(F-DRV-4) |
-
-### 2.4 驱动层缺口清单(缺什么原语)
-
-1. **无 socket 状态缓存 / 无权威回读单一来源**:7 处各自手拼 `GET_SOCKET_STATUS`
- (net_socket.c:586/1235/1694/2330/3391/3408、net_listener.c:516),三套去抖/冷却参数并存。
-2. **无类型化状态谓词**:`byte0 门控 byte1` 只写在 ch395f.h:592-601 注释里,仍有 3 处只判 `byte1`
- (net_socket.c:592/1237/3410)→ B6 类误读未收口。
-3. **无"是否在听"谓词**:`chip_listening` 判据手拼在 net_listener.c:517-518。
-4. **无复合复位原语**(排空 RX → close → 排空事件 → 有界等 CLOSED):net 层两套同构变体。
-5. **无 RX 排空原语**:`net_drain_chip_rx`(net_listener.c:102-121)是纯芯片机械。
-6. **读即清原语无护栏**:驱动裸暴露"整体读即清"的全局寄存器,B3 事故即此 API 形状的产物。
-7. **无非阻塞命令完成查询**:`poll_cmd_status` 用 `HAL_Delay(20)` 忙等,最长 4s。
-8. **无链路态缓存与边沿事件**:PHY_CHANGE/DHCP/UNREACH/IP_CONFLI 四个全局位读清后无人消费(B4)。
-9. **无版本门控**:`get_version` 只打印不判(net_socket.c:259);bit3 需 ≥0x46、bit1 需 ≥0x44(B9)。
-10. **无发送缓冲空闲查询**:SENBUF_FREE 只存在于读即清的 SINT 里 → `send_ready` 镜像无处下沉。
-11. **无 socket 索引边界校验**:51 个函数中 `sock` 一律不校验 0~7。
-12. **无 SPI 总线互斥**:串行化仅靠"只有 netTask 碰驱动"的约定(AGENTS 模块约定)。
-13. **IP 字节序契约未封装**:4 处调用点手拼大端数组,驱动注释还与实测相反。
-14. **无事件分发中枢**:`net_poll` 里的 glob→SINT→handler 分发是 net 层手拼,驱动无 dispatch 表。
-
-### 2.5 编号发现
-
-**F-DRV-1 · 同一芯片事实被 7 处独立回读、三套去抖参数并存——"单一事实源"缺失是五层膨胀的地基**
-证据:`ch395f_get_socket_status` 调用点 net_socket.c:586(对账 200ms/去抖 2/冷却 1s,:544-546)、
-:1235(探针 100ms/去抖 2,`NET_LOST_CONN_STABLE_N`)、:2330(close 等待 500ms)、:3391/:3408(事件留痕)、
-net_listener.c:516(pump 100ms/去抖 2/冷却 1s,:437-441)。驱动 static 变量只有 4 个(ch395f.c:50-56),
-无任何缓存。
-影响:同一 socket 的状态在 100ms 窗口内可被读 3~4 次,判读口径与防抖参数各不相同;任何一处口径漂移
-就产生新的"镜像说 A、芯片是 B"窗口(与 T3 的 F-MS-8 同判)。
-裁决:**下沉驱动**——驱动持每 socket 的最近一次回读 + 一个权威刷新入口(每 net_poll 周期一次),
-对账/pump/探针/close 共用同一份事实与同一套去抖。
-
-**F-DRV-2 · 读即清原语无护栏:B3 事故是驱动 API 形状的直接产物,修复落在 net 层,API 形状未变**
-证据:ch395f.c:1305(全局 16 位整体读即清)与 :1324(单 Socket 读即清)裸暴露;B3 修复只在
-net_socket.c:1122-1137 用注释收口("原为遍历 8 个 Socket 全清 + 再读清全局"→ 仅清本 Socket),
-git 考古链 `e862009bc`→`319d00e3`→`db64e6acf`(commit `4f2cc8b` 修)。
-影响:同类事故(吞掉其它在用 Socket 的 CONNECT/DISCONNECT 边沿事件)可在新调用点复发;
-这正是"看起来像 TRAP-NET-17 的自家 bug"的来源。
-裁决:**下沉驱动**——提供 `ch395f_sock_events(sn)`(只读该 Socket,永不碰全局),
-并把"全局寄存器唯一读者 = net_poll"写成驱动侧断言/文档契约。
-
-**F-DRV-3 · 无类型化状态回读:`byte0 门控 byte1` 只活在注释里,3 处只判 byte1**
-证据:ch395f.h:592-601 注释规定 `[1]` 仅在 `[0]`=OPEN 时有意义;违反口径的判读点
-net_socket.c:592(`st[1]==ESTABLISHED` 未先看 `st[0]`)、:1237(探针同型)、:3410(陈旧 DISCONNECT 甄别同型)。
-TRAP-NET-07 定案更正第 3 条正是把这类读数读成"卡在某个 TCP 态"。
-影响:幽灵态/孤儿态判定的正确性依赖每个调用点都记得这条规则——已三次失守。
-裁决:**下沉驱动**——返回 `{open, tcp_state}` 结构 + `ch395f_sock_is_listening()/is_established()` 谓词,
-`open=0` 时 TCP 态返回"无意义"哨兵值。
-
-**F-DRV-4 · "close + 排空 RX + 排空事件 + 等 CLOSED"在 net 层有两套变体,是下沉驱动的最佳落点**
-证据:net_socket.c:2326-2345(close→循环读状态 + 读 SINT + 读全局→等 CLOSED,上限 500ms)与
-net_listener.c:149-204(排空 RX→close→读 SINT→读全局→读状态→等 `CLOSED/TCP_CLOSED` + 最短等待
-400/100ms,上限 800ms)。语义同构(bit3=1 排空契约),参数漂移,**且只有后者排空 RX**。
-影响:同一芯片契约两处实现;close 路径不排空 RX,依赖"上层先排"的隐含前提。
-裁决:**下沉驱动**——`ch395f_sock_reset(sn, mode, wait_ms)`(mode=异常/正常),
-net 层只选 mode。与 T1 的 F-NET-9 同判,签名归 T4 定。
-
-**F-DRV-5 · IP 字节序契约未封装,且驱动注释与实测相反(TRAP-NET-14 的复发面仍在)**
-证据:TRAP-NET-14 根因是照抄手册"所有 IP 命令低字节在前"的总括,实测 SET_IP/SET_DES_IP 收**点分序**;
-修复落在 net 层(net_socket.c:1636-1641、:2691-2701,commit `db64e6ac`)。但驱动侧:
-`ch395f_set_des_ip` 头注释完全不写字节序(ch395f.h:479-484),`set_ip_addr/set_gwip/set_mask`
-仍写"低字节在前"(ch395f.h:380/:387/:394),而 `get_remot_ipp` 是**小端**(ch395f.h:612)。
-影响:同一驱动内两种字节序、注释还指错方向;4 个调用点各自手拼数组,新调用点极易再犯。
-裁决:**下沉驱动**——驱动内做字节序转换(对外收 `uint32_t` 网络序或明确点分序),
-并改正 ch395f.h 三处注释;这是"让 BSD API 更主流"里成本最低、收益最直接的一步。
-
-**F-DRV-6 · FUN_PARA 语义宏是死代码,net_init 传裸 `0x08`**
-证据:ch395f.h:204-206 定义 `CH395F_FUN_PARA_TCP_SERVER/SOCKET_CLOSE/DISABLE_SEND_OK`,
-全仓零引用(grep 仅命中定义处);net_socket.c:270 写 `ch395f_set_fun_para(0x08)`。
-影响:TRAP-NET-15 的"bit0=1 多 Socket 模式"伪根因能存活一年多,正是因为语义没有类型化到宏/枚举层,
-只能靠注释与手册对质。
-裁决:**简化**——net_init 改用宏组合(`CH395F_FUN_PARA_SOCKET_CLOSE`),删掉未使用的
-`CH395F_GINT_STAT_SOCK0..3`(net_poll 用 `1U<<(i+4)`,net_socket.c:464)。
-
-**F-DRV-7 · 驱动对超限写只告警不拦截——现状正确,但契约必须显式化**
-证据:ch395f.c:1183-1189 明确"超出不会被命令拒绝,而是写穿发送缓冲(TRAP-NET-16)→ 只告警不拦截,
-分块责任在调用方";分块上限 `NET_SEND_CHUNK_MAX` 在 net 层(net_socket.c:1745,应用于 :1788/:2717)。
-影响:若驱动内自动分块,需在分块间等 SENBUF_FREE,而该事件由 net_poll 集中读清 → 驱动内等待会自锁。
-裁决:**保留**(现状合理);把"单次写 ≤ `CH395F_SEND_BUF_SIZE_BYTES`"从注释升格为
-驱动 README 的显式契约,并把 OVERSIZE 金丝雀计数暴露给上层(当前只打日志)。
-
-**F-DRV-8 · 驱动的命令完成等待是阻塞原语:每条 socket 命令至少 20ms、最坏 4s 的 netTask 忙等**
-证据:ch395f.c:654-670 `poll_cmd_status` 用 `HAL_Delay(20U)` 忙等(不让出 CPU);
-`open_socket`/`close_socket`/`tcp_listen`/`tcp_connect`/`tcp_disconnect` 全部以 4000ms 上限调用它
-(:1091/:1106/:1121/:1136/:1151);DMA 路径另有 1s 忙等(:138-160)。
-影响:D10 的"非阻塞"只覆盖 net 层策略层,驱动层仍是阻塞原语——netTask 停摆指纹
-(`req=0x77`/`mask=0x41`)与 P2b 暂缓的"忙等"问题,真正的根在驱动层。
-裁决:**下沉驱动**——新增非阻塞 `ch395f_cmd_done()`(单次查询,返回 BUSY/终态),
-把 open/listen/connect 拆成"发命令"+"查完成"两步;net 层的 D10 重试即可直接复用。
-
-**F-DRV-9 · 阻塞重试三处残留,其中两处属应用层——与 D10 非阻塞设计矛盾,构成第 6/7 层自愈**
-证据:net_socket.c:1092-1104(`net_listen_locked` 3×`osDelay(500)`)、:1647-1659
-(`net_connect_locked` 同型);应用层 lftpd_inet.c:39-70(5 轮,`vTaskDelay(2000)`)、
-lftpd.c:473-477(3 轮 ×200ms)。
-影响:TRAP-NET-09 的"启动形态"已被就绪屏障 + 启动期等 PHY link 消除(net_task.c:118-130/:232),
-应用层重试的原始理由已不存在;保留它只会把恢复时间拉长并制造额外的重建所有者。
-裁决:**简化**——net 层两处并入 D10(P2b);应用层两处**可删**(保留 lftpd 的重试前先确认
-`net_relisten` 语义已覆盖,见 T1 的裁决)。
-
-**F-DRV-10 · 无版本门控:`get_version` 只打印不判**
-证据:net_socket.c:259 `DBG_INFO("ch395f ver=0x%02X fun_para=0x08", ch395f_get_version())`;
-驱动无版本判断(ch395f.c:310)。FUN_PARA bit3 需芯片 ≥0x46、bit1 需 ≥0x44(B9)。
-影响:换料/降版本时 `0x08` 静默失效,而整套排空契约(bit3=1)正是建立在 bit3 生效之上。
-裁决:**简化**——`net_init` 内门控(版本不足则拒绝置 bit3 并留自检记录),一行判定消掉整类静默。
-
-**F-DRV-11 · 旧机理的"必须一次读空整个接收缓冲"约束已事实作废,但注释仍在四处约束后人**
-证据:ch395f.h:146(旧注释"接收方向需一次性读空整个 Socket 接收缓冲…避免部分读导致剩余数据不可见")、
-net_socket.c:1908/:1969/:3466/:3470。而 `net_recv_locked` 常态按 `min(len, recv_len)` 部分读
-(net_socket.c:1882-1952),TC-NET-604 结案已撤回"部分排空致命",TRAP-NET-18 回归 10/10+5/5 全在部分读下通过。
-影响:注释与代码事实相反;`CH395F_SPI_DMA_BUF_SIZE = 2048+4` 的 sizing 理由(ch395f.h:143-153)
-仍挂在一条未证实的机理上。
-裁决:**可删**(注释级)——把旧机理降级为"未证实的历史观察",补一条"部分读→再查 recv_len"
-定向 TC 结案;DMA 缓冲 sizing 改以"接收缓冲 2KB"为唯一理由。
-
-**F-DRV-12 · 死声明与零调用 API 清单(驱动 API 面虚胖)**
-证据:`CH395F_CMD_GET_GLOB_INT_STATUS` 0x29(ch395f.h:36,只有 2 字节版 0x19 有实现)、
-`CH395F_SPI_BEGIN_DELAY_MS`(ch395f.c:42,从未使用)、EEPROM/GPIO/SLEEP 命令
-(ch395f.h:163-167 等)、`CH395F_GINT_STAT_SOCK0..3`(:244-247)、
-`ch395f_tcp_disconnect`(ch395f.c:1145,产品零调用,仅 TC-NET-204 用例 ch395f_test_task.c:783)、
-`ch395f_get_unreach_info`(:1402,全仓零调用)、`ch395f_clear_recv_buf`(:468,仅未注册的
-ch395f_reuse_bench.c:169)。
-影响:API 面越大,"哪个是产品路径"越难判;TRAP-NET-07 的"disconnect→close"错误序列正是从
-一个零调用的 API 出发的。
-裁决:**可删/登记**——删死宏与未实现命令声明;`tcp_disconnect` 保留但注释标注"产品路径禁用";
-`clear_recv_buf` 保留(TRAP-NET-20 已证"缓冲重分配无效",排渣用读空而非 CLEAR_RECV_BUF)。
-
-**F-DRV-13 · 链路事件静默:四个全局中断位读清后无人消费,驱动无链路缓存**
-证据:net_socket.c:441 读全局后只按 `bit4..bit11` 分派 socket 事件(:464),
-bit0~bit3(UNREACH/IP_CONFLI/PHY_CHANGE/DHCP)被丢弃;`ch395f_get_unreach_info` 零调用;
-链路只在启动期等一次(net_task.c:118-130)。
-影响:运行期拔网线/链路抖动/IP 冲突/ARP 不可达完全静默,只能靠芯片 TIMEOUT(≈1.6s)间接暴露。
-裁决:**下沉驱动**——驱动持链路态缓存 + 变化计数 + `ch395f_link_up()` 谓词,net 层订阅;
-这是 T3 的"PHY/链路变化收在驱动层"结论的落点。
-
-**F-DRV-14 · 中断路径实证:纯轮询无 ISR,且不应改为 EXTI**
-证据:`Src/gpio.c:76-80` PE5 = `GPIO_MODE_INPUT`/`GPIO_NOPULL`;`EXTI9_5_IRQn` 归 TP_BUSY(PC7,
-gpio.c:170-171、stm32f4xx_it.c:391);消费节奏 = net_task.c:241-279 主循环。
-影响:与 AGENTS 模块约定一致;改 EXTI 会让 ISR 与 netTask 争抢"整体读即清"的全局寄存器(F-DRV-2 形状)。
-裁决:**保留**——但把"为什么不用 EXTI"从口头约定升格为驱动 README 的显式理由(当前只有一句约定)。
-
-**F-DRV-15 · HAL 全局回调跨模块耦合:CH395F 驱动代管 SPI1 完成标志**
-证据:ch395f.c:180-199 的 `HAL_SPI_TxRxCpltCallback`/`HAL_SPI_ErrorCallback` 同时处理 SPI2 与 SPI1,
-并 `extern volatile uint8_t g_spi1_dma_done`(:178,定义在 GD5F 驱动里)。
-影响:HAL 全局回调只能有一份,谁定义谁就代管别人;改 CH395F 驱动可能静默破坏 NAND 的 DMA 完成路径。
-裁决:**简化**——收进一个 SPI 适配层(或按实例注册),至少把该耦合写进两个模块的 README。
-
-**F-DRV-16 · 单连接复用不是偏离主流;差距在驱动不持事实、不推事件、无复合事务原语**
-证据:T3 的 F-MS-2/F-MS-5(ioLibrary 无 accept、CH395 每 Socket 一连接、FreeRTOS+TCP
-`REUSE_LISTEN_SOCKET` 同形);本仓驱动 static 变量仅 4 个(ch395f.c:50-56)、无事件分发、
-无复合原语(§2.4 缺口 1/6/7/14)。
-影响:把"要不要改成 per-socket 独立状态机"当成问题是问错了方向——芯片侧本来就是
-每 Socket 一连接、监听与数据通道复用;缺的是驱动把芯片事实**收口**成一份可查询的真相。
-裁决:**保留模型**;按 F-DRV-1/2/3/4/8/13 补驱动原语后,五层里的对账/pump/探针失去存在理由,
-隔离排渣下沉为驱动原语(与 T3 的 4.3/4.4 一致)。
-
-### 2.6 内聚评估:驱动持缓存 + 权威回读后,reconcile/pump/drain 能否下沉或删除
-
-| 机制 | 能否因"驱动持事实"而删 | 理由 |
-|---|---|---|
-| TRAP-NET-17 对账①② | **不能删,但可大幅收窄** | 根因是芯片丢中断(TRAP-NET-20 裸机台架独立实证)。驱动持缓存后,对账的"回读"部分变成读缓存;"发现失配"仍需存在,只是从"周期 SPI 轮询"降为"每 poll 周期一次刷新 + 比对" |
-| N-32 pump | **不能删** | pump 是"单一重建所有者"(不变量 II),是架构解不是补丁;驱动持事实后它的判据读缓存,动作仍在 net 层(策略) |
-| D10 重试 | **可降级** | 若驱动提供非阻塞 `cmd_done()` + `sock_reset()` 内联有界等待(F-DRV-4/8),独立 30ms/2s 定时器可退化为事务内重试(T3 同判) |
-| TRAP-NET-18 排渣隔离 | **可下沉** | 排空/等待是纯芯片机械;net 层只保留"异常/正常"两档 mode |
-| 探针 `net_lost_connect_probe` | **不能删,可去抖简化** | 丢 CONNECT 是芯片行为;驱动持事实后去抖可去掉(读缓存无瞬态噪声),保留"状态已 ESTABLISHED 而本层 LISTENING → 补 accept" |
-| `auto_relisten` | **不能删** | 芯片永不自动回 LISTEN,这是模型承诺 |
-
-**一句话结论**:五层里有四层(对账、pump、探针、resync)存在的直接理由是"驱动不持事实、net 层只能自建镜像";
-把它们删掉的前提不是"根因已修",而是"驱动成为唯一事实源"。TRAP-NET-18 的排渣隔离则相反——
-它的根因(在途数据落 RX)不可修,但它的**实现位置**应当从 net 层下沉到驱动。
-
-### 2.7 对 T4 汇总的输入
-
-1. 驱动层最该补的三个原语(按收益/成本排序):
- ① 类型化状态回读 + 每 socket 缓存(F-DRV-1/3);② `ch395f_sock_reset()` 复合复位(F-DRV-4);
- ③ 非阻塞 `ch395f_cmd_done()`(F-DRV-8)。三者都不改芯片行为,只把事实与机械收进驱动。
-2. 立即可做、零风险的清理:F-DRV-6(用宏)、F-DRV-10(版本门控)、F-DRV-11(撤旧"部分读归零"注释约束)、
- F-DRV-12(删死声明)、F-DRV-5 的注释更正。
-3. 需要重测的量化项:B3 修复(`4f2cc8b`)前后各跑 `:21 100ms×50`,统计 `reconcile:` 命中数,
- 量化"自家制造的丢中断"占比(与 T1 的 F-NET-8 同一实验,勿重复做)。
-4. 与 T3 的分歧点:T3 的 4.2 第 5 条称"FUN_PARA_TCP_SERVER(多连接模式)已在 net_init 启用"——
- **与代码不符**。net_socket.c:261-270 明确 `bit1=0`(多连接模式不用),实传 `0x08`;
- 设计文档的 A′ 出局论证(不支持多监听)成立。T4 汇总时须以代码为准。
-
-
----
-
-## 3. 主流嵌入式 socket 栈对照研究(T3)
-
-> 审计组发现文件(mainstream-benchmark)。性质:web 检索 + 一手源码阅读 + 本项目代码对照。
-> 结论先行:**五层自愈补丁不是"芯片怪癖的必然代价",而是"软件镜像 + 纯事件驱动 + 决策不看芯片"这一组合的补偿税**。
-> 主流方案(含同为硬 TCP 栈的 WIZnet 官方库)没有一处维护第二份 socket 状态、也没有一处做周期对账;
-> 芯片怪癖(丢中断、close 沉降、RX 残渣)在任何架构下都存在,但主流把它们**内联进事务、收在驱动层、以有界超时兜底**,
-> 而不是在 net 层养一组常驻定时器。
-
-### 3.1 研究范围与方法
-
-- 一手源码:WIZnet ioLibrary_Driver `socket.c`(全文通读)、Zephyr `eth_w5500.c` + `eth_wiznet.c`(全文)、
- FreeRTOS-Plus-TCP `FreeRTOS_Sockets.c`(accept/listen/reuse-socket 路径)、mbed WIZnetInterface
- `WIZnetInterface.cpp`(socket_listen/socket_accept/socket_bind 路径)、WIZnet 官方 lwIP 移植
- `w5x00_lwip.c`、PeterBorisenko `spiif.c`(W5500+lwIP+FreeRTOS)。
-- 文档:W5500 官方 TCP 应用笔记、W5500 socket API 文档、CH395 数据手册、FreeRTOS+TCP API 参考、Zephyr 文档。
-- 本项目对照:`net_socket.h` 公共 API 全量、`net_listener.c` 模块头与策略常量、
- `BSD_Socket_API_使用指南.md` 第 3.6 节与第 12.10 节(五层自愈现状表)。
-
-### 3.2 各主流方案状态同步机制对照
-
-| 方案 | 状态事实源在哪 | 事件 vs 轮询 | 谁拥有状态机 | 监听/accept 语义 | 兜底机制 |
-|---|---|---|---|---|---|
-| **WIZnet ioLibrary**(W5500/W6100,硬 TCP 栈,与 CH395F 同类) | **芯片 Sn_SR 寄存器,每次调用直接回读**;软件里只有传输记账(`sock_io_mode`/`sock_is_sending`/`sock_remained_size`/`sock_pack_info`),**无 socket 状态镜像** | 调用内阻塞轮询(`while(getSn_CR)`、`while(getSn_SR != …)`);Sn_IR 标志可读可清,INT 引脚电平驱动、可选 | **芯片**(硬件 TCP 状态机);host 端没有状态机 | **没有 accept()**——监听 socket 本身就是连接 socket(与本项目单连接复用同形);重监听 = 应用主循环每轮 `switch(getSn_SR)` 回读后 open/listen | 无需对账(无镜像可对不上);命令完成/状态迁移一律有界轮询 |
-| **W5500 + lwIP**(官方 RP2040-HAT-LWIP-C、PeterBorisenko、d-a-v、QNEthernet) | 芯片**不持有 socket 状态**(socket 0 配成 MACRAW,纯网卡);socket/TCP 状态全在 lwIP pcb | 驱动任务 = INT 引脚信号量 + **500 ms 超时兜底**(超时则清 IR + 回读 PHYCFGR 对账链路) | **lwIP**(软件栈单一所有者) | 标准 BSD:listen pcb 常驻,`tcp_accept` 回调交付**新 pcb** | 驱动层超时兜底,netif 层无感 |
-| **mbed OS WIZnetInterface**(硬 TCP 栈 + nsapi BSD 门面) | **芯片 Sn_SR**(`is_connected`/`is_closed` 每次回读芯片) | accept 内轮询 `is_connected` 带超时;无状态镜像 | **芯片**;门面只翻译 API | **accept 内联重建监听**:连接继承原芯片 socket,随即 `new_socket()`+bind+listen 换一颗芯片 socket 继续监听(用 8 颗硬件 socket 模拟"监听常驻") | 重建失败处理粗糙(注释"监听已不存在"仍返回 0)——说明内联重建仍需有界重试,但重试在同一事务/模块内 |
-| **FreeRTOS+TCP**(纯软件栈) | pcb 内 `eTCPState`,**唯一写者 = IP task** | 每 socket 一个 `xEventGroup`(`eSOCKET_ACCEPT`/`eSOCKET_CONNECT`/…),调用方阻塞在事件上,不轮询 | **IP task(单所有者)** | 默认:listen socket 常驻,accept 生成**子 socket**;`FREERTOS_SO_REUSE_LISTEN_SOCKET` 模式:监听 socket 自身转连接(与本项目同形),回 LISTEN 由栈状态机处理 | 无对账(状态即事实);reuse 模式 `listen()` **同步清空 rx/tx 流并复位状态位** |
-| **Zephyr**(原生 net 栈 + WIZnet 驱动) | 驱动 = **MACRAW 网卡**(socket 0),socket 状态全在 Zephyr net 栈;**W5500 硬 socket offload 未实现**(feature request 未落地) | 驱动线程:INT 引脚 GPIO 回调给信号量 + `k_sem_take` **超时兜底**(超时 → 回读 PHYCFGR 更新链路);TX 完成 = SENDOK 信号量 + 超时 | **net 栈**(socket);**驱动**(链路态缓存 `ctx->state.is_up`,边沿通知 `net_eth_carrier_on/off`) | 标准 BSD:listen 常驻、accept 返回新 socket | 事件 + 周期回读兜底,全部收在驱动层 |
-
-关键来源(均为上表证据出处):
-
-- ioLibrary `socket.c`(socket/listen/connect/recv/send 全部直接 `getSn_SR` 回读;`socket()` 先 `close()`;`close()` 末尾 `while(getSn_SR != SOCK_CLOSED)`):
-- W5500 官方 TCP 应用笔记(每个阶段给出"方法一:Sn_IR 中断标志 / 方法二:Sn_SR 回读"双轨;明确 Sn_IR 会重叠丢失、不推荐纯事件):
-- WIZnet socket API 文档("Berkeley API 有 bind()/accept(),WIZnet API 没有"):
-- ioLibrary issue 127(listen 后立即来 SYN:`getSn_SR` 观察不到 LISTEN 瞬态——纯回读模型同样要处理瞬态,ioLibrary 的 listen() 直接 close 重来):
-- W5500 状态机由应用每轮 `getSn_SR` 驱动的典型参考流程: 与
-- W5500 INT 引脚为电平驱动(事件不可靠的硬件根因之一):
-- Zephyr 驱动: 与 (`wiznet_thread_interrupt`:信号量超时 → `update_link_status`;`wiznet_command`:写 Sn_CR 后有界轮询回读)
-- Zephyr W5500 硬 socket offload 未实现(feature request):;MACRAW 模式说明:
-- WIZnet 官方 lwIP 移植(MACRAW + lwIP 拥有全部 TCP 状态):
-- PeterBorisenko W5500+lwIP+FreeRTOS(`spiif.c`:信号量 500 ms 超时 → `spi_if_clr()` 清 IR + `spi_if_check_link_state()` 回读 PHYCFGR 边沿通知 lwIP):
-- 同类移植:、
-- mbed WIZnetInterface(`socket_accept` 内联换芯片 socket 重监听):
-- FreeRTOS+TCP `FreeRTOS_Sockets.c`(`FreeRTOS_accept` 阻塞在 `eSOCKET_ACCEPT` 事件;`prvAcceptWaitClient` 交付子 socket 或 reuse 模式返回父 socket;reuse 模式 `listen()` 内 `vStreamBufferClear(rx/tx)` + 位复位 + `vTCPStateChange(eTCP_LISTEN)`):
-- FreeRTOS accept 文档("默认创建子 socket 处理被接受连接"):
-- CH395 数据手册(TCP Server 模式:Socket 持续检测连接事件、每 Socket 同时只允许一条连接;多连接模式使能位;Socket 状态枚举含 SYN_RCVD/TIME_WAIT 等 11 态;SINT_STAT_* 事件位):(镜像 )
-
-### 3.3 提炼的设计原则
-
-1. **单一事实源(P1)**:连接状态的权威副本只有一份——硬栈芯片在 Sn_SR/芯片状态寄存器,软栈在协议栈 pcb。
- 任何"决策点"(accept 前、recv 前、重监听前)要状态就**当场回读**,而不是查一份可能滞后的镜像。
- ioLibrary 每个 API 内部都在回读;mbed 门面 `is_connected` 也回读。镜像只允许作为**展示缓存**,不得作为决策依据。
-2. **状态机单所有者(P2)**:芯片(硬栈)或协议栈任务(软栈)是唯一写者;上层通过**事件订阅**获知迁移
- (FreeRTOS 每 socket 事件组、Zephyr carrier on/off、lwIP netif link 回调)。本项目 netTask 串行化已满足"单写者",
- 缺的是"镜像降级为投影"。
-3. **事件只负责提前唤醒,正确性靠回读 + 有界超时兜底(P3)**:W5500 官方应用笔记明确 Sn_IR 会因处理不及时
- 而重叠丢失、不推荐纯事件;Zephyr/PeterBorisenko 移植全部是"INT 信号量 + 超时后回读对账"。
- 区别在于:**兜底回读收在驱动层、且回读的是芯片事实本身**,所以不存在"对账修正镜像"这种动作。
-4. **监听常驻是 API 层承诺,不是芯片属性(P4)**:芯片单 socket 事实由适配层补齐——子 socket(软栈)、
- 换芯片 socket 内联重监听(mbed)、或栈状态机内联回 LISTEN(FreeRTOS reuse)。**重监听发生在
- accept/close 事务内部**,不是独立周期机制;失败重试也在同一事务/模块内有界进行。
-5. **"复用前清理"属于资源所有者、内联进事务(P5)**:ioLibrary `socket()` 先 `close()`、`close()` 内联等待
- CLOSED;W5300 erratum 内联在 `close()`;FreeRTOS reuse 模式 `listen()` 同步清空 rx/tx 流。
- 芯片残渣/沉降怪癖的主流处理 = 打开/监听事务的前置步骤(有界超时),不是常驻排渣定时器。
-6. **分层边界(P6)**:驱动层独占全部芯片交互 + 芯片侧缓存(链路态、命令完成)+ 怪癖内联处理;
- net/栈层只见事件与数据。主流方案中**没有 net 层直接读芯片寄存器做对账**这回事——要么 net 层根本不碰芯片
- (软栈),要么决策即回读(硬栈门面)。
-
-### 3.4 映射到本项目:目标架构草案
-
-#### 3.4.1 分层职责
-
-| 层 | 应持有 | 现状偏差 |
-|---|---|---|
-| `ch395f` 驱动层 | 全部 SPI 事务;芯片 socket 状态回读(`GET_SOCKET_STATUS_SN` 已有);每 socket 事件读取即清(`GET_INT_STATUS_SN` 已有);全局中断汇聚(现在在 net 层 `net_poll`,应下沉);链路态缓存 + 边沿事件(`GET_PHY_STATUS` + PHY_CHANGE 位,Zephyr 模型);**复合复位原语**(沉降/排渣内联) | 中断汇聚在 net 层;无复合复位原语;沉降/排渣策略在 net 层 |
-| net 层(netTask 单所有者) | fd ↔ 芯片 socket 映射、消息队列串行化(保留)、L3 pending recv(保留)、事件分发/回调;**socket 状态 = 按需回读芯片,或事件推送的展示缓存** | `net_sock_state_t` 8 态镜像是决策依据(`net_can_accept`/`net_can_read`/accept/recv 全查镜像)→ 镜像滞后即产生五层补丁 |
-| 应用/服务层 | 只管 fd 与数据;监听常驻由 net 层承诺 | 已符合(EOF 不得 close 监听 fd 的约定正确) |
-
-#### 3.4.2 驱动层应新增/收编的原语
-
-1. `ch395f_sock_state(sn)`:`GET_SOCKET_STATUS_SN` 的薄封装(返回芯片 11 态枚举,映射到 net 层语义由 net 层做)。
-2. `ch395f_sock_events(sn)`:读即清每 socket 中断位;`GET_GLOB_INT_STATUS` 汇聚从 `net_poll` 下沉驱动。
-3. `ch395f_sock_reset(sn, mode, timeout_ms)`:**复合原语** = 排空 RX(`CLEAR_RECV_BUF_SN`/分块读空)→
- `TCP_DISCONNECT`/`CLOSE_SOCKET_SN` → 排空中断位 → 有界等待芯片回 CLOSED。把 TRAP-NET-09 沉降窗口与
- TRAP-NET-18 排渣隔离收进这一个同步原语(对应 ioLibrary `close()` 内联等待、FreeRTOS `listen()` 内联清流)。
-4. `ch395f_wait_cmd(sn, cmd, timeout_ms)`:命令完成有界等待(`ch395f_poll_cmd_status` 已有雏形,补齐到所有命令路径)。
-5. ~~多连接模式语义利用~~(**T4 更正,T2 代码实证**):`FUN_PARA_TCP_SERVER`(bit1)**未启用**——
- `net_init` 实传裸 `0x08`、bit1=0(net_socket.c:261-270),设计文档 A′ 出局论证(多连接模式
- 不支持多监听)成立。"芯片自己保证监听常驻"**不成立**,监听常驻只能由软件承诺(auto_relisten);
- 重监听应收敛为"reset 原语 + 一次 LISTEN 命令",由 accept/close 事务内联或单一 reconciler 触发。
-
-#### 3.4.3 net 层可整体删除/降级的补丁
-
-| 现有机制 | 裁决 | 依据(主流对照) |
-|---|---|---|
-| TRAP-NET-17 周期对账 `net_reconcile_tick`(200 ms + 去抖 + 冷却) | **整体删除** | 无镜像即无对账;ioLibrary/mbed 决策即回读(P1) |
-| N-32 监听器 pump `net_listener_pump`(100 ms,"唯一重建入口") | **整体删除**,改为 accept 事务内联重建(mbed 模型)或 close 事务内联回 LISTEN(FreeRTOS reuse 模型) | P4:重监听是事务步骤,不是周期机制 |
-| D10 重监听重试(30 ms/2 s 独立定时器) | **降级**:内联重建失败时"下次 accept 再试"或 reset 原语内部有界重试;独立定时器删除 | mbed 内联重建 + 有界超时;TRAP-NET-09 沉降收进 `ch395f_sock_reset` |
-| `net_resync_listen`(公共 API,"静默回收"对齐) | **整体删除** | "静默回收"是 CH395 TCP Server 模式**文档行为**(芯片自动回监听),回读即对齐,不需要"仅修 MCU 状态"的 API |
-| `net_lost_connect_probe`(丢 CONNECT 中断补 accept) | **降级**:accept 判据改为回读芯片状态(SYN_RCVD/ESTABLISHED 即有连接),丢中断只影响时延不影响正确性 | P3;ioLibrary 观察不到 LISTEN 瞬态也照样靠回读自愈(issue 127 场景) |
-| `auto_relisten` 标志 + `handle_disconnect_event`/`handle_timeout_event` 重建路径 | **简化**:断开事务末尾调 `ch395f_sock_reset` + LISTEN(FreeRTOS reuse 回 LISTEN 的等价物);标志位可保留作"该 fd 是监听器"的类型标记 | P4/P5 |
-| TRAP-NET-18 沉降/隔离状态机(`net_listener_settle` 400/100/800 ms 三档) | **下沉驱动**:并入 `ch395f_sock_reset(sn, mode, timeout)`;net 层只选 mode(异常/正常) | ioLibrary `close()` 内联等待、W5300 erratum 内联先例 |
-| `net_can_accept`/`net_can_read`/`net_listener_armed` 镜像门控 | **保留但改语义**:门控值 = 驱动层事件缓存(readable 由 RECV_OK 事件置位可保留),但"非 ESTABLISHED 一律返回 1"这类镜像补偿逻辑删除——决策点回读芯片后不再需要 | P1 |
-| L3 pending recv、消息队列串行化、网络代次号 | **保留**(与主流不冲突:FreeRTOS/Zephyr 同样单栈任务 + 阻塞事件模型) | — |
-
-#### 3.4.4 必须保留、但应下沉/换形的芯片怪癖
-
-| 怪癖 | 任何架构都要处理 | 主流收法 |
-|---|---|---|
-| 丢中断(CONNECT/DISCONNECT/RECV_OK,INT 电平驱动 + IR 位重叠) | 是(W5500 官方笔记同款警告) | 事件唤醒 + **有界超时回读**,收在驱动层(Zephyr `k_sem_take` 超时、PeterBorisenko 500 ms);net 层不做周期对账 |
-| close 沉降窗口(TRAP-NET-09:close 后立即 open 失败) | 是(ioLibrary `close()` 内 `while(Sn_SR != CLOSED)`;listen 竞态 issue 127) | `close()/socket()` 事务内联等待 + 有界超时(驱动原语内部) |
-| RX 残渣(TRAP-NET-18:中止后残留 ~2 KB 被下一条连接读走) | 是(软栈对应物 = FreeRTOS reuse `listen()` 清流;硬栈 = 打开前 close/清缓冲) | `ch395f_sock_reset` 前置排空(`CLEAR_RECV_BUF_SN` 命令现成) |
-| 芯片自动回收/回监听(现被当作"静默回收"陷阱) | 不是缺陷——bit1=0 下的台架实证芯片行为(run8;⚠ T4 更正:非 Server 模式条款,Server 模式未启用) | 回读对齐后此"陷阱"消失;仅需在文档里把它登记为芯片语义 |
-| PHY/链路变化 | 是 | 驱动层缓存 + 边沿事件(Zephyr carrier on/off),net 层订阅;不进对账 |
-
-#### 3.4.5 结构性根源判定
-
-本项目偏离主流的不是"单连接复用"(ioLibrary 无 accept、CH395 每 socket 一连接、FreeRTOS reuse 模式全部同形),
-而是三件事的组合:**① net 层维护 8 态镜像并作为决策依据;② 状态迁移只靠中断事件推进;③ 监听重建与事务脱钩**。
-镜像滞后 → 需要周期对账(TRAP-NET-17);事件丢失 → 需要探针与门控补偿;重建脱钩 → 需要 pump/重试/沉降三件套。
-按 P1~P6 重构后,五层中的四层(对账、pump、resync、probe)失去存在理由,一层(沉降/排渣)下沉为驱动原语。
-
-### 3.5 编号发现
-
-- **F-MS-1** ioLibrary(WIZnet 官方,硬 TCP 栈权威参考)**没有任何 socket 状态镜像**:`socket()/listen()/connect()/recv()/send()`
- 每次调用直接回读 Sn_SR/Sn_IR/Sn_RX_RSR;软件状态仅四组传输记账。"周期对账"在该模型中不存在也不必要。
- 证据:socket.c 全文(见第 2 节链接)。
-- **F-MS-2** "监听 socket = 数据通道"不是对 BSD 的偏离,而是硬 TCP 栈芯片的原生主流模型(WIZnet API 明确没有
- accept();CH395 手册"每 Socket 只允许一条连接")。本项目的偏离在**镜像 + 纯事件驱动**,不在单连接复用本身。
-- **F-MS-3** 主流对丢中断的处理范式 = **事件唤醒 + 有界超时回读兜底,且兜底收在驱动/netif 层**
- (Zephyr:INT 信号量超时 → 回读 PHYCFGR;PeterBorisenko:500 ms 超时 → 清 IR + 链路对账;W5500 官方笔记:
- 事件法与回读法双轨、事件法明示不可靠)。不存在"net 层周期对账修正软件镜像"的做法。
-- **F-MS-4** CH395 的"静默回收"(peer FIN 后芯片自行回监听、不发 DISCONNECT 中断)为**台架实证芯片行为**
- (run8,当时 FUN_PARA=0x09 即 bit3=1)。⚠ T4 更正:原文称其为"TCP Server 模式文档行为"有误——
- Server 模式(bit1)未启用(net_socket.c:261-270),该行为是 bit1=0 下的实测现象而非 Server 模式条款。
- 结论不变:`net_resync_listen` 这类"仅修 MCU 状态"的 API 是镜像滞后的直接产物,回读模型下无存在理由。
-- **F-MS-5** FreeRTOS+TCP `FREERTOS_SO_REUSE_LISTEN_SOCKET` 给出与本项目模型同形的**主流参考实现**:
- 监听 socket 自身转连接、回 LISTEN 由栈的单一状态机在事务内完成;且 `listen()` **同步清空 rx/tx 流**——
- 这正是 TRAP-NET-18 排渣隔离的主流对应物(一行内联清理,而非 400/100/800 ms 三档状态机)。
-- **F-MS-6** mbed WIZnetInterface 的 accept 模型:连接继承原芯片 socket,**accept 事务内联**在另一颗芯片 socket 上
- bind+listen 重建监听(用芯片 8 socket 兑现"监听常驻"承诺)。其失败处理粗糙(可静默失去监听)说明:
- 内联重建仍需有界重试,但重试应长在事务/模块内部,而不是独立周期机制。CH395F 多连接模式已启用,该模型可移植。
-- **F-MS-7** 主流 RTOS 对硬 TCP 栈只有两种姿势:**MACRAW + 原生栈**(状态全在 MCU,Zephyr/lwIP 全部移植如此)或
- **完全 offload**(状态全在芯片,驱动代理 BSD 调用;Zephyr 对 W5500 的 offload 仍是未落地 feature request)。
- "芯片持有状态 + MCU 镜像状态"的混合姿势正是五层补丁的结构性缺口。
-- **F-MS-8** CH395 芯片状态枚举有 11 个 TCP 态(含 SYN_RCVD、FIN_WAIT、TIME_WAIT),net 层 8 态镜像是**有损投影**;
- 有损镜像必然产生"镜像说 A、芯片是 B"的窗口,五层补丁全在为这个窗口付税。决策点回读芯片 11 态
- (`GET_SOCKET_STATUS_SN` 单命令、SPI 成本微秒级)即可让窗口归零。
-- **F-MS-9** 纯回读模型同样要处理瞬态(ioLibrary issue 127:listen 后立即 SYN,Sn_SR 观察不到 LISTEN),
- 但主流把瞬态处理写成**事务内的有界重试/重来**(ioLibrary listen() 直接 close 重来),
- 不需要"连续 2 次失配去抖 + 1 s 冷却"这类镜像世界的防抖装置——去抖与冷却是对账机制的自偿成本。
-
-### 3.6 对 T4 汇总的建议输入
-
-1. 目标架构一句话:**驱动持有芯片事实并内联处理怪癖(reset 原语),net 层单所有者只做映射/串行/事件分发,
- 决策点回读或订阅,监听重建内联进 accept/close 事务**。
-2. 分阶段建议(风险从低到高):
- ① 驱动新增 `sock_state` 回读 + `sock_reset` 复合原语(现补丁原参数原样收编,行为不变);
- ② `net_can_*`/accept/recv 判据改回读(删 reconcile/pump/probe 的触发面);
- ③ 监听重建内联化(删 auto_relisten 定时器路径、D10 独立定时器);
- ④ (远期、低优先)评估 mbed 式"换芯片 socket 重监听"。⚠ T4 更正:其前提"多连接模式已启用"不成立
- (bit1=0,net_socket.c:261-270),且多连接模式不支持多监听(A′ 出局论证)——仅列为 V0.2 级评估项。
-3. 回归判据沿用现成探针(快速复连、中止复连首上传),对照 12.10 节生产验证数据即可裁决。
-
-
----
-
-## 4. 三方汇总裁决与目标架构(T4)
-
-> 审计对象:BSD Socket API 的「芯片/本层状态对账与重监听自愈」五层机制(auto_relisten / D10 重试 /
-> TRAP-NET-17 对账 / N-32 pump / TRAP-NET-18 排渣隔离)及其显式 API(`net_relisten` / `net_resync_listen`)。
-> 方法:三路独立审计汇总——net 层盘点(`docs/网络/N34-自愈审计.md` §1,F-NET-1..14)、
-> CH395F 驱动层审计(`docs/网络/N34-自愈审计.md` §2,F-DRV-1..16)、
-> 主流方案对照(`docs/网络/N34-自愈审计.md` §3,F-MS-1..9,含 T4 更正两处:多连接模式未启用、
-> "静默回收"非 Server 模式条款)。全部证据带 文件:行号,HEAD `09d6195` 实测。
-> 性质:纯审计,未改任何代码。本部分(T4)是裁决与路线,不是实施记录。
-
-### 实施状态(2026-10-13 更新)
-
-> 想先要"为什么一直不稳"的人话版(比喻 + 全景图):先读
-> `docs/网络/N34-自愈审计.md` §0。
-
-| 提交 | 内容 | 结果 |
-|---|---|---|
-| `9ac9879` | P0 止血包(M1+M2+M4-lite) | 台架验证(E7b/E8) |
-| `ae17d36` | M3 矛盾态可疑门(风暴态免疫,资产门版) | E9/E9b 全绿 |
-| `4c0a0af` | **阶段 0** 零风险清理 9 条 | E10:0/20 + 20/20 + 20/20 + 11/11 |
-| `b8c6a3d` | **阶段 1** API 面收口(对账⑤ + 删 3 API + time_sync/lftpd) | E10:全绿回归(详见 §6 E10) |
-| `13a4a40` `d346e4e` | KeepAlive 转正(`docs/芯片手册/CH395F.md` §5.56)+ **阶段 1.5** recv 语义主流化 | E11/E12 台架 |
-| `f7f1793` `83a7072` | **阶段 2** ch395f 五原语(只加不接线)+ E2/E3/E4 定案 | Phase 15 套件 20/20 |
-| `6394853` `f51c083` | **阶段 3** net 层收敛:单一 reconciler + 忙等统一 + 镜像降级 + E-02 收口(`net_drop_conn`) | 终版回归全绿(TRAP-NET-17/-18/-20、E-02 双 PASS、104、对时、integrity 15/15) |
-| `123d80a` | E6 插桩 `NET_U15_PROBE`(默认关,零成本) | ✅ E6 已裁决(2026-10-13:命中=0 → 项 4 不立项,U15 观察项) |
-| `925058a` | **收口**:lftpd 对端地址修复(`net_getpeername` + ntoa 段序)+ E6 落档 + 全库编号重排 TRAP-NET-01~21 | @test PASS + ftp_abort 10/10 + relisten 20/20 + ci_local 全绿 |
-
-阶段 0~3 与 E6 均已完成并转正;仅剩延后项
-(relisten 沉降段统一,见 §5.5.2)。阶段 1 实施过程中的一条关键台架发现
-已留痕(§6 E10):**`net_listener_armed` 必须放行 ESTABLISHED**——单连接模型
-的"待 accept"电平,漏放行时 i104(200ms 周期)抢不过对账② 的 ~400ms 去抖而整场
-饿死(18/18 FAIL → 修复后 18/18 PASS,127s→8.3s)。
-
-### 4.0 结论先行(直接回答本次审计的问题)
-
-**问:「芯片/本层状态对账与重监听自愈」可以简化或删除吗?可以内聚到 ch395f 驱动层吗?**
-
-1. **不能整体删除。** 催生五层自愈的 6 条 Trap(09/14/17/22/23/25)中,只有 2 个被根因修复消掉
- (TRAP-NET-09 的启动形态→就绪屏障;TRAP-NET-20 的②accept 竞态→closed-with-data)。其余全部落在**芯片行为**上:
- TRAP-NET-20 的裸机芯片台架(剥离 OS/net/FTP)独立复现"握手完成但 CONNECT 中断被丢"——这是
- "对账不可因固件修 bug 而删"的决定性证据(F-NET-8)。
-2. **用户的假设部分成立,但打不中五层。** 24 条 Trap 根因归类:芯片行为 12、引用/数值错误 4、
- 判读假象 3、时序误用 2、设计缺口 2(F-DRV 总表)。寄存器数值/引用错误确实存在过
- (TRAP-NET-14 字节序、TRAP-NET-02 缓冲重叠、TRAP-NET-16 FIFO 上限、B3 全量读清),但它们**不是**五层自愈的成因——
- B3(全 Socket SINT 读清)只是"自家制造的丢中断"放大器,修复后对账仍必要,只是命中数应下降(可台架量化)。
-3. **但"五层补丁"这个形态不是终态,可以大幅简化并部分内聚。** 结构性根源不是单连接复用模型
- (ioLibrary 无 accept、CH395 每 Socket 一连接、FreeRTOS+TCP reuse 模式全部同形,F-MS-2/F-DRV-16),
- 而是三件事的组合:**① 驱动完全不持事实**(ch395f.c 仅 4 个 static,纯命令转发层);
- **② net 层维护有损 8 态镜像并作为决策依据**(芯片 11 态→8 态有损投影,F-MS-8);
- **③ 同一芯片事实被 7 处独立回读、三套去抖/冷却参数并存**(F-DRV-1)。
- 镜像滞后 → 需要周期对账;事件丢失 → 需要探针与门控补偿;重建脱钩 → 需要 pump/重试/沉降三件套。
-4. **"内聚到 ch395f"对一半。** 该下沉驱动的是**事实与机械**:状态回读+缓存、事件读即清护栏、
- 排渣/沉降/等 CLOSED 复合原语、非阻塞命令完成查询、链路态缓存。
- 该留在 net 层的是**策略与所有权**:监听重建的单一决策者(pump 是 N-32 的架构解,不是补丁,
- 且消费者驱动即时重建有 P2a 台架实测支撑,删了会 `task stall`)。
-5. **立即可删的是 API 面,不是机制**:`net_relisten`(footgun,生产零调用)、
- `net_resync_listen`(语义并入对账后删)、`net_can_accept`(iec104 迁移后退役)——
- 这三个"上层手动自愈"入口正是"补丁式代码难维护"的最直观来源。
-
-### 4.1 三方裁决的调和
-
-T1(net 层视角)判"机制主体保留、API 可删";T3(主流对照视角)判"对账/pump/探针/resync 整体删除"。
-两者不矛盾,差在参照系:**T1 评的是现架构下的必要性,T3 评的是目标架构下的存在理由**。
-统一裁决(T2 的调和,F-DRV 总表):
-
-> **删除这些机制的前提不是"根因已修",而是"驱动成为唯一事实源"。**
-> 需求(丢中断兜底、监听常驻承诺、残渣清理、沉降窗口)是永恒的;形态(五层常驻定时器 + 镜像对账)
-> 是可替换的——替换为"驱动持事实 + 决策点读事实 + 重建内联进事务 + 单一 reconciler"。
-
-### 4.2 逐机制汇总裁决
-
-| 机制 | 位置(证据见 §1/§2/§3) | 汇总裁决 | 依据 |
-|---|---|---|---|
-| `auto_relisten` | net_types.h:172 | **保留**(模型基石) | 芯片永不自动回 LISTEN,监听常驻是软件承诺(P4) |
-| D10 `net_listener_retry_tick` | net_listener.c:394-432 | **短期保留+小修;长期降级** | 小修=补 pump 同型状态门(F-NET-10)+ give-up 文案(F-NET-14);长期=驱动 `cmd_done()` 后内联进事务、删独立定时器(F-DRV-8) |
-| TRAP-NET-17 `net_reconcile_tick` | net_socket.c:548-641 | **功能保留,形态重构**(阶段 1 已做①扩展=⑤) | 与 pump 判据合并为**单一 reconciler**、读驱动缓存、一套去抖(F-DRV-1);①扩展覆盖"本层 ESTABLISHED+芯片已回 LISTEN"缺口(F-NET-2)✅ 已实施(bad=5,`b8c6a3d`) |
-| N-32 `net_listener_pump` | net_listener.c:465-553 | **并入 `net_reconcile_tick`(阶段 3,2026-10-13)** | 唯一周期重建决策者=不变量 II **不变**,只是与对账合一为单一 100ms tick(形态③④/U11,判据读驱动缓存);T3"改内联"与 P2a 台架实测冲突(删消费者驱动→`[WD] task stall`)故消费者三处仍保留 |
-| TRAP-NET-18 排渣隔离 | net_listener.c:102-121/:149-204 | **下沉驱动** | 排空/等 CLOSED 是纯芯片机械;与 net_close_locked:2326-2345 是同一契约两套变体(F-NET-9/F-DRV-4)→ 收口 `ch395f_sock_reset(sn, mode, wait_ms)`,net 层只选 mode |
-| `net_relisten` | net_socket.c:3080 | **删除** ✅ 已实施(`b8c6a3d`) | 生产零调用;对健康监听器无条件 open→USE→close,自毁 ~1s(footgun) |
-| `net_resync_listen` | net_socket.c:3026 | **删除**(先并入对账①)✅ 已实施(语义=对账⑤) | 语义正确但载体错位:应是自动对账,不是手动 API(F-NET-2/F-MS-4) |
-| `net_can_accept` | net_socket.c:1427 | **删除**(先迁 iec104 到 `net_listener_armed`)✅ 已实施 | 过期门控致 :2404 丢 CONNECT 探针从不运行(F-NET-4,与已修的 :7003 探针失效同型)。⚠ 迁移时 **armed 必须补 ESTABLISHED 放行**,否则 i104 整场饿死(§6 E10) |
-| `net_lost_connect_probe` | net_socket.c:1228-1267 | **保留**;驱动持事实后去可去抖 | 丢 CONNECT 是芯片行为(裸机实证);去抖/冷却是对账世界的自偿成本(F-MS-9) |
-| L3 pending recv + 看门狗 | net_socket.c:2024-2190 | **保留** | 阻塞模型主体,与主流(FreeRTOS 事件组阻塞)同构 |
-| `session_dead` / `released_abnormal` / 幽灵甄别 / closed-with-data | net_types.h | **保留** | 芯片行为的必要建模,P1 不变量 I |
-| `net_listen_locked` 3×osDelay(500) / `net_close_locked` 500ms 忙等 | net_socket.c:1092-1104/:2326-2345 | **简化**(P2b) | 根在驱动阻塞原语(F-DRV-8);先补 `cmd_done()` 再统一走 D10 |
-| 上层每 32 轮盲收(U15) | ui_net_sink.c:552-558、iec104_serv.c:246-253 | **中期下沉** | "RX 非空但 readable=0"并入 reconciler 后上层退役(F-NET-12) |
-| time_sync 每会话 close 监听重建 | time_sync.c:182 | **简化(上层)** ✅ 已实施(`b8c6a3d`) | 违反自家"EOF 不得 close 监听 fd"铁律,主动制造 TRAP-NET-09 沉降窗(F-NET-13);改 lftpd 模式同 fd 回 accept。实测连续 6 轮推送正常(E10) |
-| lftpd 应用层阻塞重试 | lftpd_inet.c:39-70、lftpd.c:473-477 | **删除/简化** ✅ 已实施 | TRAP-NET-09 启动形态已被就绪屏障消除,重试理由不存在(F-DRV-9)。实施偏差:未走 `net_listener_try` 登记(会把 BOUND/D10 状态语义一并拖入),改单次尝试+失败即 425(E10 登记) |
-| 三处全局读清残留 | net_socket.c:1668/:2332、net_listener.c:176 | **收窄**(与驱动护栏合并做) | B3 已证"只读本 Socket SINT"可行;这三处仍在吞别的 Socket 边沿事件(F-NET-7) |
-
-### 4.3 目标架构(高内聚低耦合的落点)
-
-| 层 | 应持有 | 现状偏差 |
-|---|---|---|
-| `ch395f` 驱动 | **唯一事实源**:每 socket 状态缓存 + 类型化回读(`{open, tcp_state}` + `is_listening()/is_established()` 谓词,byte0 门控 byte1 收口);事件护栏 `sock_events(sn)`(永不碰全局寄存器);复合原语 `sock_reset(sn, mode, wait_ms)`(排空 RX→close→排空事件→有界等 CLOSED,收编 TRAP-NET-09/-18 参数);非阻塞 `cmd_done()`;链路态缓存+边沿事件;IP 字节序封装;版本门控 | 驱动零状态(4 个 static);读即清裸暴露;命令原语全阻塞(`HAL_Delay(20)` 忙等、4s 上限);字节序注释与实测相反 |
-| net 层(netTask 单所有者) | fd↔socket 映射、消息队列串行化、L3 pending、事件分发;**状态镜像降级为展示投影**;**单一 reconciler**(对账+pump 判据合并、读驱动缓存、一套去抖);重建=`sock_reset`+LISTEN(pump 周期 + 消费者即时,两处入口语义统一) | 8 态镜像是决策依据;7 处手拼回读三套参数;两套排空变体;`net_can_*` 镜像门控 |
-| 应用/服务层 | 只管 fd 与数据;监听常驻由 net 层承诺 | 32 轮盲收、time_sync close、lftpd 重试——三层兜底散在应用 |
-
-一句话:**驱动持有芯片事实并内联处理怪癖,net 层单所有者只做映射/串行/分发且决策读事实,
-监听重建收敛为"reset 原语 + LISTEN"由 pump 与事务共同触发,应用层零自愈责任。**
-
-### 4.4 分阶段路线(风险从低到高;每阶段独立可回退)
-
-- **阶段 0 · 零行为风险清理**(纯注释/宏/一行判定,可先行提交):
- F-DRV-5 字节序注释更正、F-DRV-6 FUN_PARA 用宏、F-DRV-11 撤旧"部分读归零"注释约束、F-DRV-12 删死声明、
- F-NET-11 注释漂移(50ms→100ms)、F-NET-14 give-up 文案、F-DRV-10 版本门控(一行)、
- F-NET-6 U12 LISTEN 失败补 close(一行)、F-NET-10 retry_tick 状态门(一行)。
-- **阶段 1 · API 面收口**(小 diff,行为可测):删 `net_relisten`;iec104 门控迁 `net_listener_armed`
- → 退役 `net_can_accept`;对账①扩展覆盖静默回收形态 → 删 `net_resync_listen`;
- time_sync 会话收尾不 close;删 lftpd 应用层重试。
-- **阶段 2 · 驱动原语**(新增不删旧,行为不变):类型化回读+每 socket 缓存、`sock_events` 护栏、
- `sock_reset` 复合原语(现补丁参数原样收编)、`cmd_done()` 非阻塞、链路缓存。
-- **阶段 3 · net 层收敛**(核心重构,需完整台架回归):对账+pump 合并单一 reconciler(读缓存、
- 一套去抖)、探针去抖删除、两处忙等改走 `cmd_done`、U15 数据面对账内化 → 上层 32 轮盲收退役。
-- **贯穿验证腿**:B3 修复前后各跑 `:21 100ms×50` 统计 `reconcile:` 命中数,量化"自家制造的丢中断"
- 占比(F-NET-8,T1/T2 同一实验勿重复做);阶段 3 回归判据沿用生产验证数据
- (快速复连 :7003 10/10、:21 100ms/0ms/600ms 各 50/50、中止 RST 10/10 + FIN 5/5、SHA-256 全匹配)。
-
-### 4.5 红线(本次审计明确"不做"的事)
-
-1. **不改 EXTI/ISR**:全局事件寄存器整体读即清,ISR 读寄存器会把事件从 netTask 抢走——正是 B3 事故形状(F-DRV-14)。
-2. **不启用多连接模式(FUN_PARA bit1)**:不支持多监听,A′ 出局论证成立;T3 原文误称"已启用"已更正。
-3. **不删消费者驱动即时重建**(accept/recv/pending 三处):P2a 台架实测删除→CLOSED 窗口拉长→`[WD] task stall`。
-4. **不改单连接复用模型**:这是硬 TCP 栈原生主流形态,不是对 BSD 的偏离。
-5. **SPI 分频 8、中断经 `net_poll` 集中读清**:PCB 约束与既有约定不变(护栏是收窄读法,不是改路径)。
-6. 驱动缓存成本每 socket 数字节可忽略,但按平台约束前后跑 `scripts/ram_report.py` / `stack_report.py`。
-
-### 4.6 发现索引与后续登记建议
-
-- §1~§3 共 **39 条编号发现**:F-NET-1..14(net 层)、F-DRV-1..16(驱动层)、F-MS-1..9(主流对照)。
- 每条带 文件:行号 证据与裁决,后续排期直接按编号引用。
-- 建议登记进 `docs/任务规划/README.md`(**按铁律在 done 时统一处理,任务期不改登记簿**):
- 阶段 0 合并为一条 N-x(零风险清理簇);阶段 1 一条(API 收口簇);阶段 2/3 各一条(驱动原语 / net 层收敛,
- 后者需完整台架回归);F-NET-2 覆盖缺口建议**单独登记并优先**——它是当前唯一"无自动机制认领"的失同步形态。
-- §1~§3 即审计留痕;销项时结论沉淀进对应模块文档,本合并文档可归档。
-
-
----
-
-## 5. 实施方案
-
-> 依据:`docs/网络/N34-自愈审计.md` §4(裁决与路线)+ §1~§3(F-NET/F-DRV/F-MS 编号引用)。
-> 纪律:**每个阶段的实施以对应实验拿到明确结果为前置门**(映射见 §5.1);实验用实验分支/实验宏,
-> 不污染产品路径。所有改动只发生在 `task/N34-bsd-selfheal-audit` 分支。
-> 每阶段收尾三件套:`@build` 0 错 0 警 + `ci_local.py` + 对应台架回归(判据见各阶段)。
-
-### 5.1 实验 → 实施阶段 的门禁映射
-
-| 实施项 | 前置实验 | 实验要回答的问题 |
-|---|---|---|
-| 阶段 0(零风险清理) | 无(E0 已佐证) | — |
-| **P0 止血包(M1+M2+M4-lite)** | **E7/E7b ✅ 已完成** | 背靠背 STOR 失效循环能否断链(结论:8%→96.7%,簇已断,见 §6 E7b 节) |
-| 阶段 1(API 收口 + F-NET-2 缺口修复 + **M3**) | E5 ✅(形态罕见,顺带修) | 静默回收形态在生产路径是否真实可达、检测延迟多大 |
-| 阶段 1.5(recv 语义主流化,**待用户确认排期**) | 依赖阶段 1 对账①先落地 | — |
-| 阶段 2(驱动原语) | E2、E3、E4 | 回读成本;GET_CMD_STATUS 判别力;reset 序列的正确内部顺序 |
-| 阶段 3(net 层收敛) | E1 ✅、E6 | 对账/pump 真实命中频率(已量化);U15 失配频率 |
-
-### 5.1.5 P0 止血包(E7b 已验证,✅ 已转正 `9ac9879`)
-
-三处一行级改动(分支已实施并台架验证,**已提交**):
-
-| # | 改动 | 位置 | 效果(E7b 实测) |
-|---|---|---|---|
-| M1 | GRACEFUL 沉降无条件化(删 `recv_bytes!=0` 门) | net_socket.c:3424 | 断"被杀会话免沉降→残渣杀下一条"繁殖链 |
-| M2 | lftpd 数据等待门控 `p_sock->state` 直读 → `net_listener_armed` | lftpd_inet.c:117 | 镜像门控退役首例;救回窗可用 |
-| M4-lite | 删沉降段全局读即清(模块约定违规点) | net_listener.c:178 | 断"沉降热路径搁浅他 Socket 事件"放大器(**必须与 M1 同批**,E7 实证) |
-
-回归:n30 116/120(残余偶发自愈)+ ftp_regression 11/11 + :7003 50/50 + iec104 18/18 +
-TRAP-NET-18 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见 §6 E8 节与
-盘点 F-NET-17):残余并非稳定 3%,而是**风暴态**——同固件故障率随残渣存量 2%~54% 摆动;
-治本 = M3(`0x05/0x00`+年轻连接的电平确认,交对账①去抖)+ M5(drain-until-quiescent 的
-`sock_reset`,沉降/pump 让位 closed-with-data 救回)。M3/M5 属阶段 1/2,非止血包范围。
-**常备回归补挂**:`n30_repro --n 20` 纳入 Ch395F 回归集(本轮 46/50 潜伏的测试面根因);
-`ftp_abort_probe.py` 已迁 `test/host/`(2026-10-13,N34 阶段 3 收尾,`rename_ref.py` 全库引用同步)。
-
-### 5.2 阶段 0 · 零风险清理(先行,不依赖实验)✅ 已完成(`4c0a0af`,E10 验证)
-
-> 实施结果与下表逐条对应;提交切分因 0-A 与 0-5 改动在同一文件内交织而
-> **合并为一笔**(8 项,`4c0a0af`),0-3/0-4/0-6 的逻辑改动在提交信息中逐条
-> 列出并有冒烟证据。实施中额外抓出并修复:`CH395F_FUN_PARA_SOCKET_CLOSE`
-> 重复定义(首版增量构建 9 条 `#47-D` 警告,已并入既有宏块)。
-> 验证:全量重编 0 错 0 警 + @test PASS + n30 0/20、:21 @0ms 20/20、
-> :7003 20/20、ftp_regression 11/11。
-
-| # | 改动 | 位置 | 验证 |
-|---|---|---|---|
-| 0-1 | 注释漂移:pump "每 50 ms"→100 ms | net_socket.c:3384 | @build |
-| 0-2 | give-up 文案改"重试预算耗尽,交 pump 周期接管" | net_listener.c:414-419 | @build + @test 冒烟 |
-| 0-3 | U12 对称:TCP_LISTEN 失败补 `ch395f_close_socket` | net_listener.c:227-230、net_socket.c:1112-1114 | @build + @test(TRAP-NET-17 快速复连子集) |
-| 0-4 | retry_tick 补状态门(仅 LISTENING/CLOSED 才重试) | net_listener.c:400-412 | @build + @test |
-| 0-5 | FUN_PARA 用宏 `CH395F_FUN_PARA_SOCKET_CLOSE` 替代裸 0x08;删 `CH395F_GINT_STAT_SOCK0..3` 死宏 | net_socket.c:270、ch395f.h:244-247 | @build |
-| 0-6 | 版本门控:ver<0x46 则拒置 bit3 + 自检留痕 | net_socket.c:257-270 | @build + @test(启动锚点) |
-| 0-7 | IP 字节序注释更正(set_ip/gw/mask 实为点分序;get_remot_ipp 小端) | ch395f.h:380/:387/:394/:479-484 | @build |
-| 0-8 | 旧"部分读归零"注释降级为"未证实历史观察";DMA 缓冲 sizing 理由改"接收缓冲 2KB" | ch395f.h:143-153、net_socket.c:1908/:1969/:3466/:3470 | @build |
-| 0-9 | 删死声明:`CH395F_CMD_GET_GLOB_INT_STATUS`(0x29)、`CH395F_SPI_BEGIN_DELAY_MS`、EEPROM/GPIO/SLEEP 命令宏 | ch395f.h:36/:42/:163-167 | @build |
-
-提交切分:0-1/0-2/0-7/0-8/0-9 一笔(纯注释/死码);0-3/0-4 一笔(一行逻辑,需 @test);0-5/0-6 一笔(init 路径,需 @test)。
-
-### 5.3 阶段 1 · API 面收口(门:E5 结果)✅ 已完成(`b8c6a3d`,E10 验证)
-
-> 6 项全部实施,两个偏差与一条关键台架发现见**§6 E10**:
-> ① `net_listener_armed` 必须补 **ESTABLISHED 放行**(首版漏放行 → iec104
-> 18/18 全 FAIL,修复后 18/18 PASS 且 127s→8.3s);② 第 6 项未走
-> `net_listener_try` 登记(会把 BOUND/D10 语义拖入),改单次尝试+失败即 425。
-> 验证:iec104 18/18、n30 0/50、:21 @0ms 50/50、:7003 50/50、
-> ftp_regression 11/11、Trap23 RST/FIN 5/5+5/5、time_sync 连续 6 轮正常。
-
-1. **对账①扩展**(先修缺口再删 API):`net_reconcile_tick` 判据①增一形态——
- 本层 ESTABLISHED 且芯片 `open=1 && TCP=LISTEN` → 按 RESYNC 语义只改本层
- (ESTABLISHED→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),
- **不发芯片命令**(纪律同 pump③ U11)。位置:net_socket.c:588-590 判据处 + 复用 :789-797 的 resync 分派体。
-2. **删 `net_relisten`**(net_socket.c:3080-3120 + 消息分派 :800-814 + 头文件声明 + 指南同步)。
- 台架 Phase 13 的调用(net_latency_test.c:784/:787/:800)改用 `net_listener_try` 语义
- (经 test 专用内部入口或删该调用——Phase 13 本意是"确认连接已死后的确定性重建",pump 已覆盖)。
-3. **删 `net_resync_listen`**(第 1 步并入后;net_socket.c:3026-3066 + 分派 :774-799 + 声明)。
- net_latency_test.c:783 同步删。
-4. **iec104 门控迁移**:iec104_serv.c:227 `net_can_accept` → `net_listener_armed`(sim 已有实现);
- 随后删 `net_can_accept`(net_socket.c:1427)。
-5. **time_sync 会话收尾不 close**:time_sync.c:182 删 `net_close`,回外层循环同 fd `net_accept`(lftpd 样板)。
- ⚠ 需确认 :8888 协议语义(对端每次会话新建 TCP)——E5 顺带验证。
-6. **删 lftpd 应用层阻塞重试**:lftpd_inet.c:39-70(5×2000ms)、lftpd.c:473-477(3×200ms)——
- 就绪屏障已消除 TRAP-NET-09 启动形态;PASV 监听失败改走 `net_listener_try` 登记。
-
-验收:`@build` + `ci_local` + 台架回归全套现成脚本(`ftp_regression.py`、`n30_repro.py --n 50`、
-:7003 快速复连、`iec104_master.py`、`time_sync_server.py`)+ `@test` 锚点。
-风险:删 API 是**公开面收缩**——模拟器 `net_socket_sim.c` 同名实现同步删,双端编译验证。
-
-### 5.3.5 阶段 1.5 · recv 语义主流化(用户提议 2026-10-12,方向已确认,排期待批)
-
-对齐 Python/POSIX:**模式归使用者、超时归调用方、API 不强加默认预算**。
-
-1. `net_recv()` 默认**无限阻塞**(删 `NET_RECV_TIMEOUT_MS` 30s 默认);唤醒源=数据/EOF/close/
- 网络重建(`CONNRESET`)——会话死亡由对账①在 ~400ms 内判死兜底(**前置:阶段 1 对账①扩展先落地**,
- 否则 F-NET-2 形态是真实挂死窗)。
-2. `net_recv_nb()`(现 DONTWAIT 转正)+ `net_setblocking(fd, on)` 模式挂 socket 属性。
-3. 超时=调用方策略:首选 `net_select(r,w,e,timeout_ms)`(已有);`net_recv_to(fd,buf,len,ms)`
- 保留为**显式预算**便捷入口(等价 `settimeout`,默认值删除)。
-4. 内部 watchdog 降级为 tripwire:pending 超 60s 只 `[ERR]` 留痕(完成逻辑缺位探针),
- **不再伪造 TIMEDOUT 返回值**。
-5. **文档契约(嵌入式特有,Python 没有)**:IWDG `required_mask` 内任务**禁止无限阻塞 recv**,
- 必须 select 超时或显式预算——"超时归使用者"对受监控任务是义务不是选项。
-6. 收益:32 轮盲收(U15 上层兜底)随"阻塞为主 + 决策点回读"直接退役(阻塞 recv 的数据检测走
- `GET_RECV_LEN` 回读,对丢 RECV_OK 天然免疫——E1/E5 实证);调用点迁移面=全仓 grep
- `net_recv`/`net_recv_to`(time_sync/iec104/ui_net_sink/lftpd 数处),模拟器双端同步。
-
-### 5.4 阶段 2 · 驱动原语(门:E2/E3/E4 结果)
-
-新增到 ch395f.c/.h(无新文件,无需双端注册;模拟器零依赖):
-
-1. **类型化状态回读 + 每 socket 缓存**(F-DRV-1/3):
- ```c
- typedef struct { uint8_t open; uint8_t tcp_state; } ch395f_sock_state_t;
- ch395f_sock_state_t ch395f_get_sock_state(uint8_t sn); /* 主动回读 */
- ch395f_sock_state_t ch395f_sock_state_cached(uint8_t sn); /* 读缓存 */
- int ch395f_sock_is_listening(uint8_t sn); /* open=1 && TCP=LISTEN */
- ```
- 缓存刷新点唯一:`net_poll` 每轮对在用 socket 各刷一次(成本由 E2 实测背书;
- 预计 4 监听 × ~15µs ≪ 1ms 轮询周期)。`open=0` 时 `tcp_state` 返回哨兵 `0xFF`("无意义")。
- 边界校验:sn>7 直接返回 CLOSED+哨兵并记一次 fault 计数。
-2. **事件护栏**(F-DRV-2):`uint8_t ch395f_sock_events(uint8_t sn)`——只读本 Socket SINT(读即清),
- 永不触碰全局寄存器;全局寄存器唯一读者写成驱动侧断言(文档契约 + 调试断言)。
- 三处"两级预清"(net_socket.c:1668/:2332、net_listener.c:176)改调本原语(F-NET-7 收窄)。
-3. **复合复位原语**(F-DRV-4,内部顺序由 E4 定案):
- ```c
- typedef enum { CH395F_RESET_GRACEFUL, CH395F_RESET_QUARANTINE } ch395f_reset_mode_t;
- int ch395f_sock_reset_begin(uint8_t sn, ch395f_reset_mode_t mode);
- int ch395f_sock_reset_poll(uint8_t sn); /* 0=完成 / 1=进行中 / -1=失败 */
- ```
- ⚠ **必须是非阻塞状态机**(每 net_poll 步进),不得复制 `poll_cmd_status` 式忙等——
- 400/800ms 沉降阻塞 netTask = task stall(P2a 实测)。net 层只选 mode + 读结果。
-4. **非阻塞命令完成查询**(F-DRV-8,形态由 E3 定案):若 GET_CMD_STATUS 对目标命令有判别力 →
- `int ch395f_cmd_done(uint8_t sn)`;若无判别力(Trap 记录已证 SET 类无判别力 + RESET_ALL 陈旧 BUSY 残留)→
- **完成判据改用状态回读**(open 完成=byte0 变 05H;listen 完成=TCP=01H;close 完成=byte0=00H)。
- `poll_cmd_status` 保留给内核前/测试路径,产品 socket 路径逐步弃用。
-5. **链路态缓存**(F-DRV-13):`net_poll` 读全局后把 bit0~3 喂驱动
- (`ch395f_glob_event_feed(uint16_t bits)`);驱动持 `link_up` 缓存 + 变化计数;
- net 层订阅(链路变化 → 日志 + 可选触发重配)。运行期拔网线不再静默。
-
-验收:新增原语先**不接线**(只加不删),`@build` + 单元级板测(ch395f_test_task 加 TC 用例:
-状态回读一致性、reset 状态机跑通、cmd_done 语义)+ `@test` 冒烟。RAM 增量 <64B,跑 `ram_report.py` 确认。
-
-### 5.5 阶段 3 · net 层收敛(门:E1/E6 结果;需完整台架回归)—— **已实施 2026-10-13**(最终构建 Code=211348,回归全绿)
-
-1. **单一 reconciler**:`net_reconcile_tick` + `net_listener_pump` 合并为一个 100ms tick,
- 判据读**驱动缓存**(不再各自发 SPI),统一一套去抖(连续 2 次)+ 冷却(1s);
- 覆盖形态:①本层 ESTABLISHED 而芯片 CLOSED/回收 ②本层 LISTENING 而芯片已握手(补 accept)
- ③监听 fd 芯片失位(重建)④本层 CLOSED 而芯片在听(只重同步)。
- 消费者即时重建三处保留(P2a 定案)。
- ✅ **已实施(2026-10-13)**:`net_listener_pump` 删除、并入 `net_reconcile_tick`(`NET_RECONCILE_PERIOD_MS=100`);
- 判据读 `ch395f_sock_state_cached`(唯一刷新点=`net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs);
- 去抖**分级落地**(原计划"统一连续 2 次"按 E2/E9a 数据细化):②=1 轮 / ③④⑤=2 轮 / ①=4 轮(M3 让位窗 400ms)+ 冷却 1s;
- 主循环自愈 tick 由 4 个回到 **3 个**;形态② 命中时的对端端口回读是本 tick 唯一按需 SPI。
-2. **探针去抖删除**(读缓存无瞬态噪声,F-MS-9);`net_lost_connect_probe` 判据改读缓存。
- ✅ **已实施(2026-10-13)**:探针判据读驱动缓存(零 SPI),`NET_LOST_CONN_STABLE_N` 下线;
- 两类瞬态结构性消除("刚 listen 未生效"显示 TCP=00 永不命中握手门,E2:LISTEN 可见 ≤10ms 无长尾;
- 拆链窗假 ESTABLISHED 由幽灵甄别拦截),`lost_conn_seen` 退化为 M3 可疑电平标志。
-3. **两处忙等统一**:`net_listen_locked` 3×500ms、`net_close_locked` 500ms 改走
- `sock_reset_poll`/`cmd_done` 非阻塞路径(P2b 收口)。
- ✅ **已实施(2026-10-13)**:三处(含 `net_connect_locked` open 重试)统一走 `net_sock_reset_sync`
- (驱动 `sock_reset` 原语:读空 RX→CLOSE→排空事件(quiescent)→等 byte0=00H,内置 LISTEN 态**二段 CLOSE** 补发,
- E2 补充发现;GRACEFUL 100ms 底 / 600ms 封顶);`net_close_locked` 另加 `ch395f_cmd_done(CLOSE)` 快路径,
- 并**不再读清全局中断寄存器**(F-NET-7:全局唯一读者=`net_poll`)。
-4. **U15 内化**(若 E6 证实频率>0):reconciler 增一条"GET_RECV_LEN>0 但 readable=0 → 置 readable"
- 低频对账;随后 ui_net_sink.c:552-558、iec104_serv.c:246-253 的 32 轮盲收退役。
- ✅ **E6 已裁决(2026-10-13,§6 E6 结果):命中=0(含压测)→ 不立项**,U15 登记观察项,
- 上层盲收保留现状(与 TRAP-NET-20「不留未证明的复杂度」一致;插桩代码留仓宏=0 零成本,备再武装)。
-5. **镜像降级**:`net_sock_state_t` 保留为展示/门控投影,accept/recv/can_* 决策点改读驱动缓存。
- ✅ **已实施(2026-10-13)**:`net_types.h` 注释把 8 态明确声明为"**本层会话投影**,非芯片状态事实源"
- (芯片事实源=驱动缓存,刷新点=net_poll);reconciler / 探针 / U11 重同步等按芯片状态决策的点全部改读缓存,
- 事件路径(`handle_*_event`)保留**主动 SPI**(缓存刷新在 net_poll 末尾,事件路径上必然滞后)。
-
-验收:完整台架回归 = TRAP-NET-17(:7003 快速复连 10/10、:21 100ms/0ms/600ms 各 50/50)、
-TRAP-NET-18(RST 10/10 + FIN 5/5,SHA-256)、TRAP-NET-20(n30_repro 11/11)、E-02(e02_probe.py)、
-104(iec104_master.py)、对时(time_sync_server.py)、`@test` 锚点 + `[STACK]` 水位对比。
-✅ **2026-10-13 全绿**:`@test` PASS;`[STACK]` net peak 1116/3840(70% 余量);TRAP-NET-17 `:7003` 50/50 +
-`:21` 100ms/0ms/600ms 各 50/50;TRAP-NET-18 RST 10/10 + FIN 5/5;TRAP-NET-20 n30 11/11;E-02 双 PASS;
-iec104 18/18;对时跨会话连续更新;`ftp_regression` 11/11;integrity 15/15 SHA;
-`stack_report` netTask 静态链 **1944 B 不变**(链尾改经 `net_listener_retry_tick`→`net_relisten_locked`)。
-
-#### 5.5.1 E-02 收口(N34 阶段 3 追加,2026-10-13)
-
-E-02 的最后一环不是"挂起 recv 不冻结 netTask"(阶段 1.5 已解决),而是**应用放弃会话后单连接监听被挂起对端永久占住**:
-
-- **新 API `net_drop_conn(sockfd)`**(`NET_MSG_DROP_CONN`):拆会话**保监听**——netTask 侧
- `net_pending_settle_close` 先了结挂起 recv,再 `net_sock_reset_sync(QUARANTINE)`(400ms 底),
- 本层收口 CLOSED(不清 fd),`auto_relisten` fd 即时 `net_listener_try`(失败交 D10 + reconciler 形态④);
- **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调**(`auto_relisten` 已接管)。
- timeSync 30s 预算耗尽路径已接入;模拟器(conn≠listen fd)实现为关数据连接。
-- **`net_poll` 每轮步进 `ch395f_sock_reset_poll`**(驱动契约"在 net_poll 步进";IDLE 槽纯 RAM 判零 SPI):
- 同步包装 600ms 放弃后复位机不搁浅,对端 FIN 到达即自动走完、槽位可复用。
-- **芯片定案(E-02 台架,2026-10-13)**:CH395F `close` **无 RST 语义**(TRAP-NET-07 定案更正已载)——
- 对**内核存活但不 close 的对端**发 FIN 后芯片停 `FIN_WAIT_2`(实测 0x05/0x06 长尾),监听恢复受限于对端 close;
- 对端配合则 `TIME_WAIT` 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。
- 不配合场景兜底 = 芯片 TIMEOUT/KeepAlive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-07 补充。
-
-#### 5.5.2 relisten 沉降段统一(延后项,登记不做强行收口)
-
-`net_relisten_locked` 隔离段仍是**手拼**序列(排空 RX + 主动 close + 读本 Socket SINT + 轮询状态 + 30ms 重试轮),
-未统一改走 `sock_reset` 原语。判定:它**不是忙等**(每轮短 SPI + 交重试轮询,netTask 无长阻塞),
-且 TRAP-NET-18 的沉降等级(GRACEFUL 100ms / QUARANTINE 400ms / 800ms 强制放行 < 2s 时限)是**生产已调优**的时序,
-统一改造的回归面覆盖每次断连热路径,收益只是"少一段重复代码"——按 TRAP-NET-20 纪律**延后**,
-待下一次触碰该热路径的任务顺路处理(`sock_reset` 原语已具备承接条件:二段 CLOSE、quiescent 排空、等级化最短等待)。
-
-### 5.6 横向纪律
-
-- **台架锁**:所有烧录/`@test`/探针前后 `task_worktree.py lock acquire/release bench --task N34`。
-- **烧录**:`-Rst -Run` + 回读校验(`stlink_flash.py cmp`),`-CmpFile` 排在采日志之后。
-- **实验代码**:一律实验宏门控(如 `NET_U15_PROBE`),产品构建编译消失;实验结论写进
- `docs/网络/N34-自愈审计.md` §6 后再实施,实验宏代码在对应阶段实施时删除或转正。
-- **文档同步**:每阶段合入后同步 BSD_Socket_API_使用指南、网络Socket监听生命周期设计说明、
- CH395F_Trap_Records(U12 登记)、AGENTS 模块约定(若 API 面变化)——登记簿销项在 done 时统一做。
-- **提交**:每阶段独立提交(标题 ≤100、正文 ≤80/行,`git commit -F` UTF-8 无 BOM 文件);
- 不 push(等用户指令)。
-
-### 5.7 工作量与风险
-
-| 阶段 | 预估 diff | 风险 | 回退 |
-|---|---|---|---|
-| 0 | ~80 行 | 极低 | revert 单笔提交 |
-| 1 | ~250 行(删多于增) | 中(公开 API 收缩,模拟器双端) | 按子项独立提交可逐个 revert |
-| 2 | ~350 行(纯新增) | 低(不接线) | revert |
-| 3 | ~400 行(删 3 个 tick/忙等) | **高**(核心时序重构) | 独立提交 + 完整回归门禁,失败即 revert |
-
-
----
-
-## 6. 实验方案与记录
-
-> 目的:在改任何产品代码之前,用实验把 §5 的关键假设变成**明确结果**。
-> 每个实验有:假设 → 方法 → 判据(PASS/FAIL/数值)→ 它门禁哪个实施决策。
-> 结果一律追加记录在 §6 实验记录节(含原始日志片段与统计),实施提交正文引用实验编号。
-> 台架纪律:`task_worktree.py lock acquire bench --task N34` → 实验 → `lock release bench --task N34 --bench-pass`;
-> 烧录必 `-Rst -Run` + 回读校验;串口采集先于烧录(serial-assistant `open→clear→烧录→wait-idle`);
-> 日志出现 `[ERR]`/Fault 即本轮作废。
-
-### 实验清单
-
-#### E0 · 历史串口日志分析(零成本,无需台架独占)✅ 已跑
-
-- **假设**:五层机制在生产常态下命中极少(冷路径),日志噪声主要来自 D10 重试循环。
-- **方法**:直接 grep 主工作树 serial-assistant 日志(gen=149 窗口,2026-10-12 19:38→23:05,
- 产品构建 fw build Oct 12 2026 19:31:06 suite=NONE,开机后 ~3.5 h 常态运行)。
-- **判据**:各机制命中计数 + 每次恢复的日志行数。
-
-#### E1 · 压力下对账/pump/D10 命中量化(台架,不烧录,用现网固件)
-
-- **假设**:常态下 `reconcile` 命中≈0(E0 已证 3.5h 零命中);快速复连压力下命中才出现,
- 且 B3 修复后"自家制造的丢中断"占比应显著低于 TRAP-NET-17 时代。
-- **方法**(设备跑当前产品固件,PC 侧压测):
- 1. `n30_repro.py --n 50 --size 1024`(:21 背靠背 STOR);
- 2. `ftp_regression.py`(多数据连接会话);
- 3. :7003 快速复连循环(connect→立即 close,间隔 100ms×50——新写 `test/host/relisten_stress.py`,
- 纯 stdlib,connect 成功即 shutdown(SHUT_RDWR)+close);
- 4. `iec104_master.py` 连接/断开 ×10。
- 每轮之间 serial `clear`,跑完 grep `reconcile|pump:|relisten|give up|lost` 计数 + 恢复延迟。
-- **判据**:
- - reconcile 命中数 / 50 轮(目标基线:TRAP-NET-17 时代 2/10 死亡 → 现在应 0 死亡);
- - `pump: chip not listening` 与 `relisten retry ok` 次数与间隔(量化 D10 恢复窗);
- - 端口拒绝窗(connect 被拒的间隔分布)→ 决定阶段 3 后"恢复窗 ~1s 拒绝新连接"是否仍需改善。
-- **门禁**:阶段 3 单一 reconciler 的周期/去抖参数;F-NET-7 收窄优先级。
-
-#### E5 · 静默回收形态可达性(F-NET-2 缺口,台架,不烧录)
-
-- **假设**:对端 FIN 后芯片可静默回收 Socket 回监听且不发 DISCONNECT →
- 本层滞留假 ESTABLISHED,新连接被芯片接住但本层无感知(对账①不命中,因芯片态=LISTEN 非 CLOSED)。
-- **方法**:对 :7003(或 :8888)——connect → 发一帧 → 对端立即 FIN(close)→ 等 3s →
- 立即再 connect → 发数据 → 观察设备是否响应;同时看串口日志有无 DISCONNECT/reconcile 痕迹。
- 对照组:同样序列但第二次连接前等 10s(看是否被某机制自愈)。
-- **判据**:
- - 复连后设备**无响应且无日志** → 缺口坐实(PASS=缺口存在),记录卡死时长;
- - 若被自动恢复 → 记录是哪条机制救的(日志)→ 修正 F-NET-2 结论。
-- **门禁**:阶段 1 第 1 步(对账①扩展)的判据设计与优先级;time_sync close 语义确认。
-
-#### E2 · 状态回读成本与瞬态窗口(裸机芯片台架,需烧实验固件)
-
-- **假设**:`GET_SOCKET_STATUS` 单次 SPI 成本为微秒级 → "决策点回读/每 poll 刷新"成本可忽略;
- 且"listen 后立即 SYN 观察不到 LISTEN"瞬态窗口有界(ioLibrary issue 127 同形)。
-- **方法**:复活 `test/board/ch395f_reuse_bench.c`(存档态:注册进 uvprojx Test 组 +
- main.c USER CODE 2 区调用,实验分支不入库该注册);DWT 周期计数器测
- `ch395f_get_socket_status` 单次耗时 min/avg/max;矩阵中在 LISTEN 命令后 0/10/50/100ms 回读
- 记录首次观察到 TCP=01H 的延迟。配套 `ch395f_reuse_host.py` 打连接。
-- **判据**:单次回读 ≤100µs(预计 ~15µs)→ 每 poll 全量刷新成立;
- LISTEN 可见延迟分布 → 决定 reconciler 去抖是否可减为 1 次。
-- **门禁**:阶段 2 缓存刷新策略;阶段 3 去抖简化。
-
-#### E3 · GET_CMD_STATUS 判别力矩阵(裸机芯片台架,同场烧录)
-
-- **假设**:GET_CMD_STATUS 对 OPEN/LISTEN/CLOSE 有判别力;对 SET 类无判别力
- (Trap 记录已证 SET 类 + RESET_ALL 陈旧 BUSY 残留)。
-- **方法**:同 E2 实验固件:对每条命令(OPEN_SOCKET/TCP_LISTEN/CLOSE_SOCKET/SET_SOUR_PORT/
- SET_PROTO_TYPE)发命令后以 ~1ms 节奏轮询 cmd status,记录"变 BUSY→终态"延迟与假阳性
- (命令未发时读到的残留态)。
-- **判据**:某命令判别力成立(发后 ≤N ms 内变终态、无残留假阳性)→ `ch395f_cmd_done` 可用该判据;
- 不成立 → 完成判据改用状态回读(`docs/网络/N34-自愈审计.md` §5.4 第 4 项的回退方案)。
-- **门禁**:阶段 2 第 4 项形态。
-
-#### E4 · sock_reset 内部顺序 A/B(裸机芯片台架 + 生产路径复现)
-
-- **假设**:TRAP-NET-18 排渣只需"排空 RX(读空)→ close → 排空事件 → 等 CLOSED",
- `CLEAR_RECV_BUF` 命令可不用(TRAP-NET-20 已证缓冲重分配无效);GRACEFUL 与 QUARANTINE 差异仅在
- 是否排 RX + 最短等待。
-- **方法**:E2 实验固件加变体:close 前 (a)读空 RX (b)CLEAR_RECV_BUF (c)什么都不做,
- 随后复监听 + 立即上传,校验载荷完整性(SHA/图案);生产路径侧用 `n30_repro.py` 变体
- (RST 中止 → 立即复连 STOR)对照现固件行为。
-- **判据**:(a) 与现实现等价(10/10 载荷完整)、(b) 不等价则记录差异 → 定案 `sock_reset` 内部序列。
-- **门禁**:阶段 2 第 3 项实现细节。
-
-#### E6 · U15 失配频率(生产路径插桩,需烧实验固件)
-
-- **假设**:`RX 非空但 readable=0`(RECV_OK 中断丢失的数据面形态)在生产真实发生,
- 上层 32 轮盲收是必要兜底而非保险。
-- **方法**:实验宏 `NET_U15_PROBE` 门控:net_poll 每 32 轮对在用 fd 发一条 GET_RECV_LEN,
- 若 >0 且 !readable → 计数 + 低频日志(每小时 ≤1 条防刷屏);跑 24h 常态 + E1 压测。
-- **判据**:命中 >0 → 阶段 3 第 4 项(U15 内化)立项;命中 =0(含压测)→ 上层盲收保留现状、
- U15 登记观察项。
-- **门禁**:阶段 3 第 4 项做不做。
-
-### 实验顺序与成本
-
-| 顺序 | 实验 | 需烧录 | 预估耗时 |
-|---|---|---|---|
-| 1 | E0 | 否 | ✅ 已完成 |
-| 2 | E1 | 否(现网固件) | ~40 min(含写 relisten_stress.py) |
-| 3 | E5 | 否 | ~20 min |
-| 4 | E2+E3+E4 | 是(一次实验固件全收) | ~2 h(含注册/烧录/矩阵跑完/还原) |
-| 5 | E6 | 是(插桩固件) | 烧录 20 min + 24h 挂机(期间可做阶段 0) |
-
-E1/E5 不烧录、风险最低,先跑;E2/E3/E4 一次实验固件合并跑(实验分支,注册不入库);
-E6 挂机期间并行实施阶段 0。
-
----
-
-### 实验记录
-
-#### E0 结果(2026-10-12 23:05,gen=149 窗口 19:38→23:05,产品构建,常态运行 ~3.5 h)
-
-命中计数(grep 大小写不敏感,SimpleMatch):
-
-| 模式 | 命中 | 说明 |
-|---|---|---|
-| `reconcile` | **0** | TRAP-NET-17 对账整窗口零命中(冷路径坐实) |
-| `chip not listening` | 2 | 均为 `sock2 pump: chip not listening (0x00/0x00, was state=0) -> relisten`(pump 情形②) |
-| `relisten` | 36 | 其中 ~24 条是 `relisten quarantine/settle (sock=0x00 tcp=0x00)` **重试循环逐轮留痕**(30ms 节奏),其余为 `auto-relisten ok`/`relisten retry ok` 成功尾 |
-| `settle` / `quarantine` | 12 / 14 | sock1(GRACEFUL)与 sock2(QUARANTINE)各若干次会话收尾 |
-| `give up` / `resync` / `lost connect` | 0 | 无放弃、无探针触发 |
-
-恢复形态(原始片段归纳):会话结束 → settle/quarantine 窗口内每 30ms 一条留痕
-(sock2 QUARANTINE ≈15 条 ≈450ms;sock1 GRACEFUL ≈4 条 ≈120ms)→ `auto-relisten ok` +
-`relisten retry ok`。**每次恢复产生 5~16 行日志**——D10 重试循环的逐轮留痕是主要日志噪声源。
-
-结论:
-1. 常态下对账零命中 → 阶段 3"单一 reconciler 读缓存"不会增加热路径成本;E1 压测补压力面。
-2. 沉降/隔离与 D10 重试是**每次会话收尾都走的热路径** → `sock_reset` 复合原语(阶段 2)收益直接。
-3. 日志留痕策略需随阶段 2 收口(重试循环只留首末 + 计数,勿逐轮)。
-
-#### E1 结果(2026-10-12 23:09–23:16,现网产品固件 fw=Oct 12 19:31:06,net 代码=HEAD)
-
-压测腿与结果:
-
-| 腿 | 结果 | 延迟分布 |
-|---|---|---|
-| :7003 复连 100ms×50 | **50/50 成功,0 拒连** | p50=**1020ms** min=2ms max=1049ms |
-| :7003 复连 0ms hold50×20 | 20/20 | p50=527ms |
-| `n30_repro --n 50`(背靠背 STOR) | **4/50 成功(46 次 425)** | — |
-| `n30_repro --n 10`(复跑确认) | **1/10 成功(9 次 425)** | — |
-| `ftp_regression`(有间隔) | 11/11 | 正常 |
-
-串口命中(leg1 窗口):`reconcile`=1、`pump: chip not listening`=61、`auto-relisten ok`=70、
-`give up`=0、`listen lost`=1、`lost-CONNECT self-accept`=1。
-
-**发现 A(热路径时序,量化"恢复窗")**:正常断连的完整链 = `DISCONNECTED`(芯片 0x05/0x00,
-bit3=1 契约)→ 释放路径**只置事实不重建**(N-32 定案)→ 等 pump(实测 115–900ms)→ 首次
-`net_listener_try` 失败 → 30ms 后 D10 重试成功(~75ms)。**每次会话收尾产生 ~0.3–1s 监听空窗**;
-空窗内 SYN 被静默丢弃(无 RST),客户端 1s RTO 重传 → :7003 复连 p50≈1020ms。
-→ 直接支撑"重建内联进释放事务"(mbed/FreeRTOS reuse 模型)与 `sock_reset` 原语;
-也解释 TRAP-NET-17 时代"恢复窗拒绝新连接"的口径。
-
-**发现 B(P0,新):背靠背数据连接失效循环,现固件可复现 9/10**:
-失败签名(sock4=FTP 数据监听):数据连接 accept 后 **~20ms** 被**上一条连接的陈旧 DISCONNECT**
-判死(`DISCONNECTED, releasing (chip st=0x05/0x00)`——芯片 TCP=0x00 非 ESTABLISHED,
-**通过 D20 陈旧甄别**)→ 被杀会话 `recv_bytes=0` → **不登记 GRACEFUL 沉降**(门=收过数据)→
-立即 relisten → 下一条连接仍落在残渣投递窗内 → 再被杀——**自持循环**。`closed-with-data accept`
-偶发救回(窗口内仅 2 次)但 lftpd 数据门控(`lftpd_inet_accept_timeout` 直读 `p_sock->state` 镜像)
-错过窗口 → 5s 超时 → 425。
-对照台账:TRAP-NET-20 定案回归 = `ftp_regression`(有间隔)11/11,**n30_repro(背靠背)不在终版回归集**;
-Trap 记录证据通则节自认"历史台架计数日志未入库不可复核"——本次 9/10 是**首个入库可复核的背靠背数据**。
-初步归因指向:G1 GRACEFUL 沉降门 `recv_bytes≠0` 对"被陈旧事件杀死的零数据会话"失效(新缺口,
-建议登记 F-NET-15);G2 三处全局读清残留(F-NET-7)与事件护栏缺失(F-DRV-2)是残渣投递的供给面。
-→ 根因深挖已派 net-auditor(代码级),修复属阶段 0/1 范围(一行级沉降门修正候选)+ 阶段 2 护栏。
-
-#### E5 结果(2026-10-12 23:2x,:7003 connect→send 1B→FIN→3s→复连 ×10)
-
-20 次 FIN **全部产生 `DISCONNECTED` 事件(20/20)**;pump 重建 10 次;`reconcile`=1。
-**"静默回收"形态(本层 ESTABLISHED + 芯片回 LISTEN + 无 DISCONNECT)在 E1+E5 累计 ~110 次
-连接/断开中零复现**。
-结论:F-NET-2 缺口在代码上真实(无任何机制认领该形态),但触发在本环境罕见
-(run8 台架曾实证,非日常路径)→ 阶段 1 顺带修(对账①扩展),不单独立急项。
-
-#### E7 / E7b 结果(2026-10-12 23:36–23:5x,M1+M2 ± M4-lite 实验固件,台架锁 N34)
-
-**实验改动**(分支实验性,未提交):
-- M1 `net_socket.c:3424` GRACEFUL 沉降无条件化(删 `recv_bytes!=0` 门)
-- M2 `lftpd_inet.c:117` 镜像门控 → `net_listener_armed`
-- M4-lite `net_listener.c:178` 删沉降段全局读即清(E7 中途追加,见下)
-
-**E7(M1+M2)**:`@build` 0/0(Code=209488);`@test` PASS(戳 23:35:58,回读无差异)。
-
-| 测试 | 基线(HEAD 固件) | E7(M1+M2) |
-|---|---|---|
-| n30 ×50 | 4/50 | 37/50(尾部 13 连败**持续簇**) |
-| n30 ×20 立即复跑 | — | **20/20 全败**(簇自持且跨轮持续) |
-| ftp_regression | 11/11 | 11/11 |
-| :7003 100ms×50 | 50/50 p50=1020ms | 50/50 p50=1022ms(M1 的 +100ms 沉降被 SYN-RTO 窗掩盖) |
-
-**E7 中途根因(新发现 F-NET-16)**:失败簇期间 `disarmed`=0(M2 从未早退,425=lftpd 满 5s 超时)、
-`closed-with-data` 38 次**全部迟到**、控制通道 sock2 也被陈旧 DISCONNECT 杀。代码定位:
-`net_listener.c:178` 沉降段每 30ms 重试一轮 `GET_GLOB_INT_STATUS_ALL`(全局读即清)——
-M1 使沉降成为每次断连热路径后,**每次关闭产生 ~4 次全局读清**,按 B3 机制搁浅其它 Socket 的
-CONNECT/RECV_OK(net_poll 门控焊死)→ 数据连接事件永不到达 lftpd → 满 5s 超时。
-该行同时违反 AGENTS 模块约定「所有 CH395F 中断在 net_poll 内集中读取并清除」。
-**教训:M1 单独上会放大 F-NET-7 供给面——M1 与 M4-lite 必须同批**(net-auditor 的 M1+M2 组合
-建议需修正为 M1+M2+M4-lite)。
-
-**E7b(M1+M2+M4-lite)**:`@build` 0/0(Code=209484,−4B);`@test` PASS(回读无差异;
-注:固件戳未变=戳所在 TU 未重编,镜像身份以 Code 尺寸变化+回读无差异为准)。
-
-| 测试 | E7b 结果 |
-|---|---|
-| n30 ×50 第 1 轮 | **47/50**(#18-20 偶发,**自愈无簇**) |
-| n30 ×20 立即复跑 | **20/20** |
-| n30 ×50 第 2 轮 | **49/50**(#47 偶发) |
-| ftp_regression | 11/11 |
-| :7003 100ms×50 | 50/50 p50=1011ms |
-| iec104_master | 18/18 PASS |
-| TRAP-NET-18 RST×5 / FIN×5(`test/host/ftp_abort_probe.py --check-abort`) | 5/5(450)/ 5/5(226) |
-
-**裁决**:M1+M2+M4-lite 把背靠背 STOR 从 8% 拉到 **96.7%(116/120)**,残余 ~3% 偶发
-= 原始"滞后 DISCONNECT 杀新连接"形态(D20 结构性失明,M3 域)——**自愈型、无簇、无数据损坏**。
-自持循环已断,P0 降级为 P2 尾巴。
-**遗留**:① M3(D20 年轻连接电平确认,并入阶段 1 对账①扩展)治本剩余偶发;
-② `n30_repro` 纳入常备回归集(本轮 4 连败的测试面根因);③ `ftp_abort_probe.py` 落在
-`MDK-ARM/` 目录(应迁 `test/host/`,与"脚本在库"证据通则一致);
-④ 设备现跑实验固件,转正或回退由用户裁决。
-
-#### E8 残余偶发深度排查(2026-10-13 00:06–00:2x,提交 9ac9879 后固件,台架锁 N34)
-
-**背景**:E7b 报"残余 ~3%"后,TRAP-NET-18 abort 探针(RST×5+FIN×5)再跑 n30 → **27/50**
-(两个持续簇 #6-25、#44-50)——"3%"不是稳定值,故障率随设备状态在 2%~54% 间摆动。
-
-**实验矩阵(同固件、同负载,仅设备状态不同)**:
-
-| 设备状态 | n30 结果 | 簇形态 |
-|---|---|---|
-| 烧录后全新启动(E7b) | 3/50、1/50、0/20 | 偶发、自愈 |
-| abort 探针 + 多轮压测累积后 | **27/50** | 两个持续簇 |
-| 仅 ST-Link 复位(不重烧) | **6/50** | 两小簇(#37-38、#47-50) |
-
-→ **状态依赖确证**:故障率 ∝ 芯片侧残留(OPEN 卡死 socket + 粘滞 DISCONNECT 锁存)存量。
-
-**风暴签名(污染态,00:07:14.5–15.2 窗口,全部在 0.75s 内)**:
-```
-closed-with-data accept (drain pending) ← 救回:RX 有数据,accept 返 fd 给 lftpd
-settle ×2 ← pump 的沉降插进来
-standalone accept ← 下一条 SYN 进卡死芯片 socket
-DISCONNECTED, releasing (chip st=0x05/0x00) ← accept 后 11ms 被杀
-closed-with-data accept (drain pending) ← 又救回……循环 ×3
-pump → settle ×4 → auto-relisten ok ← 800ms 强制放行才出簇
-→ 5.6s 后 accept timeout (425)
-```
-
-**机制链(三个叠加缺陷,登记 F-NET-17)**:
-1. **沉降自再生锁存**:沉降段 `net_listener.c:174` 的 `close_socket` 在芯片已 CLOSED
- 后再 close 仍会(经拆链收尾)重新锁存 DISCONNECT;`:176` 的 SINT 读清发生在
- close 后**同一轮**,而锁存在 close 后 ~10-20ms 才落(TRAP-NET-12 实证)→ 沉降最后一轮
- 读清后残留一枚"迟到锁存",relisten 完成后 11ms 杀死首条连接。30ms 重试粒度 vs
- 10-20ms 锁存延迟 = 每次重建掷一次硬币(E7b 的 ~2% 偶发即此)。
-2. **泵与救回互抢**:沉降段 `:172` `net_drain_chip_rx` 排空的正是 closed-with-data
- 救回(`net_socket.c:1317`)依赖的 RX 数据;pump 沉降与 lftpd 救回在同一 socket 上
- 竞速——救回 accept 成功返回(`:1352`)但 lftpd 拿到的是**被杀会话/被排空半份的数据**
- → 短读 → 425。风暴期 31 次 accept timeout 与 38 次救回并存即此矛盾的直接证据。
-3. **芯片 OPEN 卡死放大**:被杀会话芯片侧仍 OPEN(0x05),新 SYN 被芯片接进卡死
- socket(TCP 槽仍 0x00)→ net 层 accept → 又被下一枚锁存杀 → 风暴自持,直到
- 800ms 强制放行 + close 真正落拆链才出簇。
-
-**对 M3/M5 设计的修正(比 T5 版更锐)**:
-- **M3 判据升级**:杀事件时刻 `chip=0x05/0x00`(socket OPEN + TCP 槽 0x00)与
- "连接年轻(<1s)"组合 = **逻辑矛盾态**(活连接应 0x04)→ 不释放,交对账①电平
- 去抖二次确认。该判据在风暴窗口 100% 命中、健康路径 0 误伤(健康断连芯片为
- 0x00/0x00 或 0x07)。
-- **M5 语义升级**:`sock_reset` 必须 **drain-until-quiescent**(close 后轮询 SINT
- 直到 DISCONNECT 锁存出现并读清、连续两轮无新锁存才 open+listen),固定 100ms
- 窗在锁存延迟抖动下必输硬币。
-- **新增红线**:沉降/pump 重建前**必须让位**于 closed-with-data 救回(RX 有数据
- = 消费者资产,先交还 accept 路径,排空后才准 reset)——泵与救回必须单一所有者,
- 归 `sock_reset` 内联裁决。
-
-#### E9 结果(2026-10-13 00:30–00:47,M3 原型三轮迭代,台架锁 N34)
-
-**T6 先修正了 E8 的机制表述**(详见盘点「F-NET-17 补充」):close 已 CLOSED socket
-**不产生**锁存(TC-NET-204 模式 C 证伪强形式);锁存生成者=拆链完成事件(滞后+粘滞);
-"沉降末轮读清不保证静默"仍成立,经"拆链滞后于末轮读清"+"剩余两处全局读清搁浅"两路径。
-M3 因此从"可选加固"升格为**结构性必需**(静默可被新连接自身流量打破=无界窗)。
-
-**M3 判据三轮迭代(每轮都是台架数据说话)**:
-
-| 轮 | 判据 | n30(预置残渣后) | Trap23 探针 | :21 @0ms×50 |
-|---|---|---|---|---|
-| R1 | `0x05/0x00 + flag==0` | **0/50** ✓ | **0/5+0/5** ✗ | — |
-| R2 | +`recv_bytes==0` | **0/50** ✓ | 5/5+5/5 ✓ | **46/50**(4 refused,p90=1540)✗ |
-| R3 | +`芯片 RX>0` | **0/50** ✓ | 5/5+5/5 ✓ | **50/50** ✓ |
-
-- **R1→R2 教训**:已读数据的会话(探针控制通道)遇 0x05/0x00 多为真死(RST/槽污染),
- 延迟它实测 **~1.2s**(对账 1s 冷却,非 T6 估的 400ms)打破应用窗口。
-- **R2→R3 语义修正**:延迟保护的本质=**"芯片 RX 有在途数据"=不可毁的消费者资产**
- (closed-with-data 救回同源红线)。@0ms 快复连(连上即关零数据)RX 空 → 立即释放,
- 误杀也无数据损失,后到数据由救回接住。
-- 最终判据四条件:`st=0x05/0x00 && lost_conn_seen==0 && recv_bytes==0 && RX>0`;
- 前置补 `handle_connect_event` 清 `lost_conn_seen`(两处,T6 发现的必要前置);
- 对账①**零改动**认领(E9 实测 71 suspect → 全部正确接管/释放)。
-
-**E9b 全套回归(R3 固件,Code=209552,0 错 0 警,@test PASS 回读无差异)**:
-n30 ×50(预置后)**0/50** + ×20 **0/20**;ftp_regression 11/11;:21 @0ms×50 **50/50**;
-:7003 50/50 p50=1007/p90=1037(回到 E7b 水平);iec104 18/18;Trap23 RST/FIN 5/5+5/5。
-
-**遗留观察(非 E9 阻塞,已归因)**::21 @0ms×50 窗口有 `[WD] task stall`(多任务 miss)
-——该窗 M3 suspect **零触发**(资产门正确让位),归因于 :21 暴力用例本身的断连 churn
-(40 DISCONNECTED + 157 settle 行/20s,E7b 时代已有 MISS start 前例);列入阶段 2
-`sock_reset` 沉降内联后的复验项。B10 的 :21 守护口径(50/50)通过;stall 是否先于
-M1 存在无基线可考(历史日志未入库,又一"脚本在库、日志未入库"实例)。
-
-**裁决**:M3(R3 资产门版)具备转正条件;风暴态免疫在"探针预置残渣"最恶劣前置下实证。
-
-#### E10 结果(2026-10-13 05:40–06:50,阶段 0 + 阶段 1 转正验证,台架锁 N34)
-
-**范围**:M3 转正提交(`ae17d36`)+ 阶段 0 九项(`4c0a0af`)+ 阶段 1 六项
-(`b8c6a3d`,16 文件 +193/-371)。三笔均 `@build` 0 错 0 警 + `@test` PASS
-(回读无差异)。
-
-**阶段 0 验证**(Code=209580):全量重编 0 错 0 警(含强制重编依赖 TU 以暴露
-头文件改动引入的警告——首次增量构建 9 条 `#47-D` 宏重定义警告即由此抓出:
-`CH395F_FUN_PARA_SOCKET_CLOSE` 与既有宏块重复定义,已并入原宏块);
-冒烟 n30 0/20、:21 @0ms 20/20、:7003 20/20、ftp_regression 11/11。
-
-**阶段 1 首版台架失败与根因(本记录的核心留痕)**:
-
-| 项 | 首版 | 根因 | 修复后 |
-|---|---|---|---|
-| iec104_master | **18/18 全 FAIL**(127s 跑满超时) | `net_listener_armed` 首版只放行 LISTENING / CLOSED+残留,**漏了 ESTABLISHED** | 18/18 PASS,**8.3s** |
-| n30 / :21 / :7003 / ftp / Trap23 | 全绿 | 未受影响(见下) | 全绿 |
-
-**机制**(为什么只有 i104 中招,是一条非显然的坑):
-
-- 单连接模型下"连接已到、应用未 accept"的电平就是 **ESTABLISHED**(CONNECT
- 处理把监听 fd 自身转数据通道)——这正是旧 `net_can_accept` 唯一放行的状态。
-- 漏放行后能否活下来纯看**消费者任务周期与对账②去抖赛跑**:
- ui_net_sink 10ms 周期 → `net_accept_locked` 内自带 `net_poll` 通常先处理
- CONNECT 并在同一次调用里返回 fd(探针/在途 accept 抢先);
- **i104 200ms 周期抢不过对账② 的 ~400ms 去抖** → 对账② standalone accept
- 先转 ESTABLISHED → armed=0 → 应用整场不 accept、芯片 RX 数据无人读。
-- 设备日志证据链:`sock1 standalone accept`(06:24:23)→ 之后整场无
- `i104: accepted`,直到对端 FIN 才由 `closed-with-data accept` 救回
- (`i104: accepted fd=1` 06:26:30)。期间主站 I 帧全部无响应。
-- **修复语义**:armed = 旧 `net_can_accept` 的 ESTABLISHED 放行态 ∪ LISTENING
- ∪ CLOSED+残留。此态 accept 必返 fd,一次调用即离开分支,**无 RPC 风暴风险**
- (P2a 的 task stall 约束针对 CLOSED 无残留态,不适用)。
-
-**M3 无辜旁证(顺带实证)**:同一窗口 `sock1 suspect DISCONNECT on live conn
-(chip 0x05/0x00), defer to reconcile` 命中 → 对账① 1.2s 后接管 →
-`closed-with-data accept (drain pending)` 把**未被 accept 的 RX 数据**救回
-——正是 M3 资产门的设计意图(芯片 RX 有在途数据 = 不可毁的消费者资产)。
-
-**阶段 1 全绿回归(修复版,Code=209580,@test PASS)**:
-iec104 **18/18**(8.3s,较基线 127s 大幅缩短:连接即刻被 accept);
-n30 ×50 **0/50**;:21 @0ms×50 **50/50** p50=512;:7003 100ms×50 **50/50**
-p50=517;ftp_regression **11/11**;Trap23 RST **5/5** + FIN **5/5**。
-
-**time_sync 第 5 项(会话收尾不 close)实测**:连续 6 次推送成功(RTC 逐次
-更新),每轮 `standalone accept → DISCONNECTED, releasing → relisten settle →
-auto-relisten ok → 下一轮 accept`(周期 ~4s = PC 侧 2s 宽限 + 2s 间隔),
-零 `[ERR]`。契约变化已同步 `test/host/time_sync_server.py` 注释:板子不再先
-关,PC 侧 2s 宽限后自行关闭。⚠ 注意:若 PC 进程被**强杀**(非正常关闭),
-芯片侧会走 `[ERR] sock0 TIMEOUT` + QUARANTINE 沉降(400ms)再重建——本轮
-首次验证即因强杀验证进程复现一次,属异常收尾,非正常路径。
-
-**阶段 1 与 §5 的两处偏差(诚实登记)**:
-
-1. 第 6 项原计划"PASV 监听失败改走 `net_listener_try` 登记",实施时判定该路
- 径会把 armed/BOUND/D10 状态语义一并拖入(需给 `net_relisten_locked` 补
- `auto_relisten`、给 armed 补 BOUND 放行、改 0-4 状态门),代价与一个罕见
- 故障不匹配 → 改为**单次尝试 + 失败即 425**(主流 FTP 服务器 passive
- listen 失败亦如此,客户端自带传输重试语义),并删除 `cmd_pasv` 的
- 3×200ms 重试。
-2. 第 2/3 项原计划拆分两笔提交,实施时因 0-A 与 0-5 的改动在同一文件内交织
- → 阶段 0 合并为一笔(0-3/0-4/0-6 的逻辑改动在提交信息中逐条列出并有
- 冒烟证据),阶段 1 一笔。判据与验证不受影响。
-
-**编译面验证(阶段 1 附带)**:`net_latency_test.c` 的 Phase 13 清理位于
-`#ifdef ENABLE_PHASE13_TESTS` 内、产品构建不编译 → 临时开 CH395F 套件 +
-Phase13 宏全量重编 **0 错 0 警**(Code=216632)后还原宏再重编产品镜像,
-确认死代码无语法/符号残留。
-
-**收尾门禁(`ci_local.py`,退出码 0)**:L0 pre-commit 钩子 **32/32 通过
-(0 失败 0 跳过)**、L0b 提交信息 gitlint **10 条 0 失败**、L0.5 主机侧单测
-**15 runner / 951 断言全过**。过程中修掉两处真实收尾问题(各一笔提交):
-
-1. **time_sync 单测契约残留**(`b3ef728`):阶段 1 第 5 项删掉
- `time_sync_handle_conn` 的 `net_close` 后,4 条主机侧断言仍要求"处理完关闭
- 连接"(TC-TS-020e/023b/024b/027c)→ 改为 `s_close_calls == 0` 并补契约变更
- 注释(任务级关闭由 `conn != ls` 分支负责,TC-TS-040e 仍覆盖)。80/80 PASSED。
-2. **分支级 RAM 台账漂移**(`bfbe24e`):本分支自身代码增长(net_listener.c 等)
- 从未刷新 RAM 文档受管区 → mem-numbers / mem-budget 在分支上长期失败
- (任何 App/*.c 提交都会撞),此前靠 `--no-verify` 绕过。用项目工具
- `check_mem_numbers.py --write` + `ram_baseline.py --write` 同步:ER_IROM1
- 227,824→228,172 B、Flash 合计 228,928→229,276 B;**RAM 两区未变**
- (RW_IRAM1 97,864 B/74.7%、RW_CCM 57,376 B/87.9%),印证阶段 0/1 未动 RAM。
- 此后本分支常规提交不再需要 `--no-verify`。
-
-#### E11 结果(2026-10-13 07:01–07:18,CH395F 服务器侧 KeepAlive,临时实验固件)
-
-**问题**:`CMD_SET_KEEPALIVE_SN`(手册 §5.56)——芯片在"IDLE 无收发 → 探测
-CNT 次无 ACK"后产生 SINT_STAT_TIM_OUT / DISCONNECT。怀疑本仓没有利用它。
-
-**静态证据(怀疑成立)**:
-
-| 事实 | 证据 |
-|---|---|
-| 参数配了:idle 60s / intvl 5s / cnt 3(均 500 倍数、IDLE>INTVL) | `net_config.h:61-70`,`net_socket.c:298-302`(INIT_CH395 之前) |
-| 开关**全仓仅一处**,且在 TCP **客户端**分支 `CONNECTING` | `net_socket.c:3257` |
-| `App/` 目录**零个 `net_connect()`** → 生产无任何 socket 会走到该行 | grep 全仓 |
-| 手册明确:TCP **服务器**应在 `SINT_STAT_CONNECT` 后开启 | `docs/芯片手册/CH395F.md` §5.56(:815-821) |
-| 服务器侧调用 2026-10-12 曾加后**撤回**,理由仅覆盖"物理链路失效";注释自认"链路在、对端静默"未覆盖且**无法注入** | `net_socket.c:3223-3229` |
-| 唯一能发现该缺口的 Phase 11 测试**默认关闭** | `test/board/ch395f_test.h`(`//#define ENABLE_PHASE11_TESTS`) |
-
-**注入手段的两次修正(方法学留痕)**:
-
-1. **Windows 防火墙入站 DROP **无效****:连接建立后再加规则,对该已建立连接
- 不生效(ALE 层在连接建立时判定)。正对照实测:装规则后 KEEPLIVE 回显**照常
- 收到** → `VERDICT: injection INEFFECTIVE`(`e11_inject_check.py`)。这也解释了
- 首轮 235s 窗口无 TIMEOUT(对端一直在 ACK,并非 keepalive 失效)。
-2. **有效注入 = 禁用 PC 网卡**:中间有交换机(判别依据:ESTABLISHED 会话下
- 禁用 PC 网卡 12s,设备**零网络事件** → 设备侧 PHY 链路保持)。此时对端不可达、
- 设备链路在、且不产生 FIN/RST —— 正是"链路在、对端静默"。
-
-**实测(实验固件:`handle_connect_event` LISTENING 分支加
-`ch395f_set_keepalive_enable(sock,1)` + 调用点 DBG 留痕;TCP :7003)**:
-
-| 试验 | 配置/注入 | 结果 |
-|---|---|---|
-| T2 正常空闲存活 | 生产参数 60/5/3,对端存活但静默 90s | KEEPLIVE 回显正常、**无 TIMEOUT** → 不误杀空闲会话 ✓ |
-| T4 定标(短参数) | 6s/2s/2,禁用 PC 网卡 40s | 留痕 `keepalive enabled (idle=6000 intvl=2000 cnt=2)`;**T+5.1s** 报 `[ERR] sock3 TIMEOUT` → 参数按配置缩放,芯片默认值(20/15/9)假设**被否** ✓ |
-| T5 生产参数 | 60/5/3,禁用 PC 网卡 90s | 留痕 `idle=60000 intvl=5000 cnt=3`;**T+30.0s** 报 `[ERR] sock3 TIMEOUT` → QUARANTINE → relisten → **新连接成功恢复** ✓ |
-
-**结论**:
-
-- KeepAlive 能力**真实可用**,且本层**已有现成消费者**(`net_poll` 派发
- TIM_OUT → `handle_timeout_event` QUARANTINE → pump 重建)——打开开关即可闭环。
-- 生产参数下实测判死 **≈30.0s**(早于手册公式 75s;芯片定时语义与手册公式
- 不一致,**以实测值为准**)。短参数实测 ≈5.1s,证明参数生效且可按需调优。
-- 静默但**存活**的空闲会话不被误杀(T2)——因为对端会 ACK 探测包。
-- 2026-10-12 撤回的盲区(链路在、对端静默)**正是本机制覆盖的场景**;当年
- "+2s 即报 TIM_OUT"只覆盖了物理断链。
-- **已采纳转正(2026-10-13,用户批准)**:在 `handle_connect_event` 的 LISTENING
- 分支加回一行 `ch395f_set_keepalive_enable(sock, 1)`(手册规定位置),并重新打开
- Phase 11 测试纳入回归;参数是否调优(如缩短 IDLE)由实测判死窗口需求决定。
-- ⚠ 台架副作用:本轮反复实验后设备落入当时称为"残渣风暴"的状态(详见 E12)。
- **该状态已由 E13 重新定性为僵尸会话,30~90s 自动清除,无需冷启动。**
-- **产品路径验证(2026-10-13 10:32,转正提交后产品固件)**::7003 连接 →
- 禁用 PC 网卡 51s → `[ERR] sock3 TIMEOUT`(**禁用后 17s**)→ QUARANTINE →
- pump → `auto-relisten ok` → 网卡恢复后新连接 PASS(KEEPLIVE 回显正常)
- → 转正的一行在**产品固件**上闭环生效 ✓
-
-#### E12 结果(2026-10-13 07:18–08:05,SPI 缓存区 TSL 时序 A/B,临时实验固件)
-
-**问题**:手册 §7.4——SPI/并口模式下读写收发缓存区命令**只需**在"长度与数据
-之间"留 TSL(读 0.4us / 写 0.2us),其余字节间无需间隔;**非缓存区命令**另有
-TSC=0.6us(命令→数据)、TSD=0.3us(数据字节间)。本仓是否满足?
-
-**静态证据(没考虑过,且缓存区路径零间隔)**:
-
-| 事实 | 证据 |
-|---|---|
-| 全仓无任何 TSL 相关代码/注释(grep 只命中手册) | — |
-| 两个缓存区函数把 `[cmd, sock, lenL, lenH, data...]` 打包进**同一 DMA 缓冲、一次连续突发**(CS 全程拉低)→ 长度→数据**间隔 = 0** | `ch395f.c` `write_send_buf` / `read_recv_buf`(原实现) |
-| 其余命令走逐字节 `HAL_SPI_TransmitReceive(...,1,...)` → TSC/TSD 有 µs 级余量 ✓ | 同上 |
-| 量化:SPI2 分频 8 @APB1 42MHz = **5.25MHz**(单字节 1.52us);TSL 0.4us ≈ 2.1 个 SCK 周期 | `Src/spi.c:84` |
-
-**A/B 设计**:A = 现状(零间隔单突发);B = 头/数据两段传输 + 段间显式忙等
-(`CH395F_TSL_LOOPS=40` ≈1.0–1.4us,另叠加两段间 HAL/DMA 重配开销;CS 全程拉低,
-帧形与手册图一致)。B 代价:代码 +28B(Code 209580→209608)、写路径多一次
-memcpy;**吞吐无回退**(A 40×128KB 51.8s vs B 47.9s)。
-
-**字节级校验(`test/host/ftp_integrity_test.py`:STOR→RETR 全量 SHA-256 比对,
-差异自动二次回读归因)**:
-
-| 臂 | 样本 | 结果 |
-|---|---|---|
-| A(零间隔) | 260 轮(20×64KB + 240×128KB ≈32MB/方向) | **1 次不一致**(A2 轮,细节未留档,A3/A4/A5 未复现) |
-| B(两段+TSL) | 200 轮(40+100+60 ×128KB ≈25.6MB/方向) | **0 次不一致** |
-
-→ **未观察到 TSL 造成的可复现数据错位**;两臂均接近"零错位"。
-
-**连接级 n30 ×50(每次运行前重烧录 = MCU 复位 + RESET_ALL)**:
-
-| 臂 | 各轮失败数 | 有失败的轮次 |
-|---|---|---|
-| A | 8, 22, 0, 0, 12, 0, 0, 39 | 4/8 |
-| B | 0, 0, 0, 0, 0, 47 | 1/6 |
-
-**污染控制与关键反证**:序列末恢复产品镜像后再跑 n30 ×20 = **16/20 失败**,
-日志签名 = PASV 数据连接被芯片接受后立即关闭(`sock4 st=0x00/0x00`)+ 控制连接
-TIMEOUT → 即 E8 记录的**残渣风暴态**,且**跨烧录存活**(重烧 + @test PASS 后依旧)
-→ 与 E8"普通复位后仍 12%"一致。A 39/50 与 B 47/50 **两臂都出现**灾难轮
-→ 该差异**不能归因于 TSL**,E12 的连接级对比**被此状态噪声吞没,无结论**。
-> ⚠ **"跨烧录存活"口径已被 E13(2026-10-13 10:55)推翻**:板上 CH395F 的
-> RSTI 脚接 MCU NRST(`docs/硬件平台/V0.0_网表详细分析报告.md` :174
-> "RSTI←NRST")→ 每次 ST-LINK 烧录/复位都会**硬件复位芯片**;该状态实为
-> **僵尸会话**(E13),锤击几分钟内即再生,"跨烧录存活"是再生造成的错觉。
-
-**结论(诚实口径)**:
-
-1. 现状驱动**确属规格偏离**(零 TSL 间隔,按"间隔"语义解读),但**未测得实际危害**:
- 460 轮/≈58MB 双向字节级校验仅 1 次未复现异常,且两臂无差异。
-2. 依 TRAP-NET-20 铁律「不留未证明的复杂度」:**当前不建议改产品路径**,而是
- **登记该偏离 + 本轮证据**;B 变体作为**待用补丁**保留(若将来出现芯片批次
- 差异、提高 SCK、或不明数据错位,可直接采用——它已证明无吞吐/正确性回退)。
-3. **方法学教训(E13 修正版,后续时序/连接类实验必读)**:本台架的**僵尸会话
- 态**(曾称"芯片残渣风暴")必须在实验前后受控:由"对端静默死亡"(网卡禁用/
- 拔线/PC 崩溃)触发,芯片 TIMEOUT(有在途 TX ≈30s)/ keepalive(纯空闲
- ≈75-90s)自动清除;锤击会在几分钟内再生僵尸 → 同一镜像重烧后仍可
- 0/50..47/50 摆动、跨臂污染使 A/B 失效。**正确控制 = 下一臂开跑前静置
- ≥90s 让僵尸清窗**,不是断电冷启动(RSTI←NRST,烧录本身就是芯片硬件复位)。
-
-#### E13 结果(2026-10-13 10:24–10:59,风暴态根因=僵尸会话:确定性复现,产品固件+网卡抖动注入)
-
-**假设**:E8/E12 记录的"残渣风暴态"不是跨烧录存活的芯片内部残渣,而是
-**僵尸会话**(对端静默死亡 → 芯片侧 ESTABLISHED 未清)+ 单连接监听被僵尸占用 +
-PASV 重监听被垂死僵尸 accept 的竞态——且可由网卡抖动**确定性复现**。
-
-**推翻"跨烧录存活"的硬件事实**:板上 CH395F 的 **RSTI 脚接 MCU NRST**
-(`docs/硬件平台/V0.0_网表详细分析报告.md` :174 "RSTI←NRST";手册
-`docs/芯片手册/CH395F.md` :935 亦载"通过 RSTI 引脚拉低控制复位")
-→ 每次 ST-LINK 烧录/复位(`-Rst -Run`)都会**硬件复位芯片**。
-"风暴态跨烧录存活"与接线事实矛盾——存活的不是芯片内部状态。
-
-**排除 4 元组复用假设**(`test/host/pasv_probe.py`,本轮新增):5 轮干净 STOR
-打印每轮 PASV 端口 + PC 源端口 → PASV 端口**递增**(49157→49161)、PC 源端口
-随机变化 → "芯片复用 PASV 端口 + TIME_WAIT 4 元组冲突"假设**被否**。
-
-**确定性复现(100%,产品固件,零插桩)**:
-`ftp_integrity_test.py --rounds 40 --size 131072` 正常跑到第 21 轮(前 20 轮
-全 OK)→ **禁用 PC 网卡 8s**(10:55:01,打断传输中会话)→ 恢复网卡 →
-`n30_repro.py` 首连 :21 **ConnectionRefused**。
-
-**因果链全程(串口日志)**:
-
-| 时刻 | 事件 | 解读 |
-|---|---|---|
-| 10:55:01 | 网卡禁用:PC 内核直接丢弃全部 socket,**不发任何 FIN/RST** | 芯片毫不知情 → sock2/sock4 成 **ESTABLISHED 僵尸**;设备侧 30s 内零网络事件 |
-| 10:55:30 | `[ERR][NET] sock2 TIMEOUT` | 芯片**重传耗尽**(设备有未 ACK 的在途 TX)→ 僵尸清除,**+30s** |
-| 10:55:31 | `sock2 pump: chip not listening (0x05/0x00) -> relisten` → `auto-relisten ok` | 本层 pump 接住 → 监听复活 |
-| 10:55:43 | 新连接 accept → PASV → `sock4 standalone accept` → **20ms** 后 `suspect DISCONNECT on live conn (chip 0x05/0x00), defer to reconcile` | **PASV 竞态**:sock4 的僵尸(被打断轮遗留)未清,新监听器的 accept 为垂死僵尸开枪 → lftpd 5s accept 窗空转 → 425 |
-| ~10:58 | n30 ×3 全 OK | sock4 僵尸被 TIMEOUT/reconcile 清除 → 自愈完成 |
-
-**结论**:
-
-1. **风暴态根因 = 僵尸会话**,不是芯片残渣:对端静默死亡(网卡禁用/拔线/PC
- 崩溃/进程被杀)是注入源;上午 E11 反复网卡抖动 + E12 460 轮锤击
- = 完美风暴生成器。失败率随时间摆动(E8 的 2%~54%)= 僵尸生成/清除的节奏。
-2. **"跨烧录存活"是错觉**:重烧后芯片干净,但锤击几分钟内再生半死会话。
- E8/E12 的"需断电冷启动"口径作废(见上方修正标记)。
-3. **现有自愈链端到端有效**(② keepalive 转正 + pump + 对账① 的合力,
- 本轮首次在同一场景全链验证):
- - 有在途 TX 的僵尸:芯片 TIMEOUT(重传耗尽)≈30s;
- - 纯空闲僵尸:keepalive 60/5/3(E11 T5 实测 30s、产品验证 17s);
- - 本层 pump/对账接住事件 → relisten → 端口恢复。
-4. **剩余缺口(阶段 2 收口)**:PASV 重监听**不预清**数据 Socket——僵尸未清
- 就在同一 Socket 上开新监听 → accept 为垂死僵尸开枪 → 5s 窗。阶段 2
- `sock_reset`(open 前 close+等 CLOSED)直接消除该竞态。
-5. **e02_probe listener=FAIL 重解读**(非阶段 1.5 回归):探针的挂起客户端
- socket 在 PC 侧仍打开(脚本在第二次连接尝试之后才 close),PC **内核**会对
- 芯片 keepalive 探测自动 ACK → 僵尸会话"活着" → 单连接监听被占、拒新连接是
- **正确行为**;探针预期("预算耗尽 → 设备拆连接")与阶段 1-5"EOF 不 close"
- 设计冲突。真实缺口 = **应用层无法主动拆掉自己放弃的会话** → 也是阶段 2
- `sock_reset` 的职责(应用侧形态)。
-6. 顺带登记(显示层,低):`[FTP] connection received from 100.1.168.192:21`
- ——对端地址显示为设备自身 IP 的字节序反转 + 端口 21,疑似 lftpd 日志把
- local 地址当 peer 地址打印(不影响功能,未立项)。
-
-**工具与留痕**:`test/host/pasv_probe.py`(新增);pktmon 抓包留存
-`%TEMP%\n30_cap.etl`(3.9MB,含网卡抖动窗口,设备 IP 过滤)。
-
-#### E2 结果(2026-10-13 11:43–11:50,bench 实验固件 Code=213412,跑两遍,台架锁 N34)
-
-**固件**:复活 `test/board/ch395f_reuse_bench.c`(N30_CHIP_BENCH 门控,内核前裸机;
-旧 N-30 定案矩阵由 `s_bench_legacy=0` 停泊保留);注册 uvprojx Test 组 + `Src/main.c`
-USER CODE 2 区(注册不入库,实验完 `git restore`)。DWT CYCCNT @168MHz(1 cycle≈5.95ns)。
-两遍独立运行,本节数字两遍一致(第二遍为第一遍 C 变体级联污染后的干净重跑,E2/E3 复验)。
-
-**单次回读成本**(`ch395f_get_socket_status`,N=1000×2 遍,两遍逐值相同):
-
-| 统计 | cycles | µs |
-|---|---|---|
-| min | 3048 | 18.1 |
-| avg | 3057 | 18.1 |
-| max | 3481 | 20.7 |
-
-**LISTEN 可见性矩阵**(裸 TCP_LISTEN 不轮询命令态,发命令后 +0/10/50/100ms 回读;
-100ms 仍不可见则 10ms 节奏续查到 1000ms;12 轮×2 遍=24,轮间换端口):
-
-| 首次观察到 TCP=01H | 计数 |
-|---|---|
-| +0ms | 14 |
-| +10ms | 10 |
-| >10ms | **0** |
-
-**判据结论**:
-1. 单次回读 18.1µs ≪ 100µs → **"每 poll 全量刷新"成立**(4 监听 × ~18µs ≈ 72µs ≪
- 1ms 轮询周期;`docs/网络/N34-自愈审计.md` §5.4 第 1 项预估 ~15µs 量级坐实)。
-2. LISTEN 可见延迟有界:100% 在 +10ms 内可见、无长尾 → **reconciler 去抖可减为 1 次**
- (决策点后 ≥10ms 单次回读即可裁定 LISTEN 形态;现"连续 2 次"去抖对该形态无必要——
- 瞬态窗口仅存在于 0~10ms 内,且第二次回读必在窗口外)。
-
-**E2 补充发现(2026-10-13 12:23,Phase 15 板测 TC1501/1502b 实证):LISTEN 态
-CLOSE_SOCKET 是二段关闭**。CLOSE 命令对 LISTEN 态 Socket 返回 SUCCESS(0x00),
-但只拆监听(TCP 01H→00H),**byte0 恒留 05H(Socket 仍 OPEN),2s 内不自落**;
-需再发一次 CLOSE(此时 TCP 已 00H)byte0 才落 00H(二段间隔实测 0ms 即落)。
-生产 net 层早已在**无意中吸收**该语义:`net_close_locked` 等 CLOSED 的 500ms 窗
-超时后照常放行,随后重 listen 时 `net_listen_locked` 的 TRAP-NET-09 open 重试
-(3 次 × 500ms 沉降)第二次 open 前的 close 恰好充当二段——这就是 TRAP-NET-09
-"关闭后需延时才能重开"重试环存在的芯片级原因之一。阶段 2 `sock_reset_poll`
-的 WAIT_CLOSE 阶段已内置二段补发(标记=drain 字段 0xF,只补发一次),
-TC1502b 实证 LISTEN 态复位 GRACEFUL ~180ms 落定。
-**对阶段 3 的含义**:任何以 `cmd_done(DONE_CLOSE)` 为判据的等待,若目标 Socket
-可能处于 LISTEN,必须容忍"一次 CLOSE 不落 00H"——要么走 sock_reset(已内置),
-要么保留 TRAP-NET-09 式重试,不得把 DONE_CLOSE 当单次 CLOSE 的充分条件。
-
-#### E3 结果(同场同固件,5 命令 × 5 轮 × 2 遍 = 50 样本,两遍签名逐条一致)
-
-**方法**:发命令前对照读 GET_CMD_STATUS(记录残留假阳性);发命令后按手册
-`docs/芯片手册/CH395F.md` §9.2.1 节奏(≥2ms 首查)以 ~1ms 轮询至终态(2s 上限)。
-`pre`=对照读值,`first`=首个非 BUSY 终值,`busy`=是否观察到 BUSY→终态转变。
-
-| 命令 | pre(对照读) | busy | first | done | 判别力判定 |
-|---|---|---|---|---|---|
-| OPEN_SOCKET | 0x00(r0) / **0x17**(r1-4) | 否(0/10) | 0x00 | 3ms | **部分**:状态确被 OPEN 更新(0x17→0x00),但 pre=SUCCESS 时完成与残留不可分 |
-| TCP_LISTEN | 0x00(残留 SUCCESS) | 否(0/10) | 0x00 | 3ms | **不成立**:无转变,残留 SUCCESS 不可甄别 |
-| CLOSE_SOCKET | 0x00(残留 SUCCESS) | 否(0/10) | 0x00 | 3ms | **不成立**:同上 |
-| SET_SOUR_PORT | **0x17** | 否 | **0x17(不变)** | — | **不成立**:SET 类根本不更新命令状态 |
-| SET_PROTO_TYPE | **0x17** | 否 | **0x17(不变)** | — | **不成立**:同上 |
-
-**三条实证**:
-1. **残留假阳性坐实**:陈旧错误码 0x17(ERR_VAL,源自"对已关闭 Socket 再 close")
- 跨后续 SET 命令(不更新状态)持续存活——复核 `docs/问题记录/CH395F_Trap_Records.md`
- TRAP-NET-20 定案轮的"陈旧残留"形态(RESET_ALL 陈旧 BUSY 变体本矩阵未复现,同类
- 陈旧值持续存活已证)。
-2. **OPEN/LISTEN/CLOSE 均在 <2ms 完成**,BUSY 从未观察到——"BUSY→终态"转变对这三条
- 命令不可观测;驱动 `poll_cmd_status` 20ms 粒度对它们恒白等 ≥20ms(无害但无判别价值)。
-3. **SET 类无判别力**(命令状态完全不被更新)——Trap 记录结论复核成立。
-
-**判据结论(定案 `docs/网络/N34-自愈审计.md` §5.4 第 4 项)**:五条命令均不满足
-"发后 ≤N ms 变终态 **且** 无残留假阳性" → **`ch395f_cmd_done` 完成判据改用状态回读**:
-open 完成=byte0 变 05H;listen 完成=TCP=01H;close 完成=byte0=00H
-(回读成本 18.1µs 由 E2 实测背书;listen 可见延迟 ≤10ms 由 E2 矩阵背书,成本与时效均成立)。
-GET_CMD_STATUS 仅保留两用途:INIT_CH395 类慢命令(内核前/测试路径)与**命令级被拒**
-检测(非 SUCCESS 非 BUSY 错误码仍会被芯片写入命令状态,B2 判据仍有效——被拒码是
-"新值覆盖残留"方向,不受残留歧义影响)。`poll_cmd_status` 保留给内核前/测试路径,
-产品 socket 路径弃用。
-
-#### E4 结果(同场同固件 + 生产路径基线;3 变体 ×10 轮 ×2 遍 = 60 轮)
-
-**方法**:每轮先造污染会话(主机 `test/host/ch395f_reuse_host.py --mode e4`:连上 →
-发 3000B → RST,TRAP-NET-18 中止形态),芯片侧按变体执行复位序列,**立即**原端口重开
-+ LISTEN,主机立即上传 4096B 图案载荷(图案种子随全局轮次变,上一轮残渣必失配)。
-PASS 口径 = 消费者在 DISCONNECT/TIM_OUT 前实际读到的字节数=4096 且图案逐字节匹配
-(= 生产 net 层释放语义;事后读空仅作取证,不计入 PASS)。
-
-| 变体 | 第 1 遍 | 第 2 遍 | 观察 |
-|---|---|---|---|
-| A 读空 RX→CLOSE→排事件→等 byte0=00H | **10/10** | **10/10** | 19/20 轮 rxdrain=**2048**(残渣恰等于芯片单 socket RX 缓冲整容 = TRAP-NET-18 机制直接证据) |
-| B CLEAR_RECV_BUF→CLOSE→排事件→等 CLOSED | **10/10** | **10/10** | 与 A 等价,无差异 |
-| C 什么都不做→CLOSE→等 CLOSED | **0/10** | **0/10** | 首 C 轮(rg20)两遍同签名,随后**自持级联** |
-
-**C 变体失败签名(两遍逐字节复现)**:重开后主机载荷连接 SYN 被吞(主机侧
-connect 超时);bench 首次 SINT 读 = **0x04**(陈旧 RECV 位粘滞——TRAP-NET-12
-"粘滞到被读",close/open 不清 SINT);后续轮污染连接亦 SYN 被吞(主机
-`contam CONNECT timeout`、bench `no death event`)——每轮 close 又携带未排空数据
-→ **级联自持**,open+listen 命令照常返回 SUCCESS 也无法恢复,唯芯片硬件复位
-(E13 RSTI←NRST)清除。
-
-**机制定案**:决定性一步 = **close 前 RX 缓冲必须为空**(读空与 CLEAR_RECV_BUF
-等价——TRAP-NET-20"缓冲重分配无效"指的是 set_send/recv_buf 重分配,不是 CLEAR)。
-带非空 RX 关闭违反 `docs/芯片手册/CH395F.md` §5.52 bit3=1"读出残留数据后再关闭"
-契约 → 芯片拆链停滞 → 下一会话 SYN 被吞/CONNECT 丢失,且自持。排空事件(A/B 有、
-C 无)清的是粘滞 SINT 位——对**事件驱动 accept 的正确性**必需(0x04 陈旧 RECV 会
-误导消费者),但不是 TCP 层吞 SYN 的根因。所有轮 closed_ms=0(close 命令落定后
-byte0 已是 00H)——"等 CLOSED"几乎零成本,但为契约必需步。
-
-**生产路径基线**(烧 bench 固件前,Phase 1.5 产品固件 Code=209780):
-`test/host/ftp_abort_probe.py --check-abort --rounds 5` → **5/5**(中止 STOR→450;
-设备日志 `sock4 TIMEOUT` → quarantine → `UPLOAD '/ab000N.bin' FAILED` →
-followup `UPLOAD '/ab_ok.bin' 4096 bytes OK`)——现实现(隔离期持续排空 RX +
-close + 排空事件 + 等 CLOSED)行为基线全绿,与变体 A 序列一致。
-
-**判据结论(定案 `docs/网络/N34-自愈审计.md` §5.4 第 3 项)**:
-(a) 与现实现等价成立(A 20/20 载荷完整);(b) B 与 A 等价(无差异记录);
-C 不等价(0/20 + 级联)→ **`ch395f_sock_reset` 内部序列定案**:
-
-1. 读空 RX(GET_RECV_LEN→READ_RECV_BUF 直到 0;CLEAR_RECV_BUF 等价可用,**取读空**
- ——保手册 §5.52 契约且排空字节数可观测可留痕);
-2. CLOSE_SOCKET;
-3. 排空本 socket 事件(drain-until-quiescent,E8 M5 口径:连续两轮无新位);
-4. 轮询 byte0=00H(非阻塞状态机步进,约束不变)。
-
-GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排 RX + 最短等待"中
-"是否排 RX"一半被否(带脏缓冲关闭在任何释放形态下都危险),两模式差异只剩
-最短等待(100ms/400ms,TRAP-NET-18 二阶段实测值)。
-
-#### E-02 收口台架(2026-10-13,阶段 3 产品固件 Code=211348,`test/host/e02_probe.py`)
-
-**背景**:E13 第 5 条 把 `e02_probe` 的 `listener=FAIL` 重解读为"探针预期与'EOF 不 close'设计冲突",
-并指出真实缺口 = **应用层无法主动拆掉自己放弃的会话**。阶段 3 以 `net_drop_conn()` 收口该缺口,
-本节记录收口后的台架定案时序。
-
-**drop_conn 时序实测**(timeSync 30s 预算耗尽路径,:8888 单连接监听):
-
-| 阶段 | 观察 |
-|---|---|
-| 预算耗尽 | `time_sync: no TIME packet in 30s, drop conn` → netTask 侧 `sock_reset QUARANTINE`(FIN + 排渣 + 排空事件 + 等 byte0=00H,400ms 底)+ 本层收口 CLOSED + `auto_relisten` 即时 `net_listener_try` |
-| 对端不配合(PC socket 仍开) | 芯片停 **`FIN_WAIT_2`**(byte0=`05H` / byte1=`06H`)长尾——`close` 无 RST(TRAP-NET-07 定案更正),监听恢复**受限于对端 close**;复位机在 `net_poll` 步进里等待,不搁浅 |
-| 对端配合(探针 close 自己的 socket) | 芯片 `TIME_WAIT` 过渡 ~150ms(TC-NET-204 模式 A 同形)→ byte0 回 `00H` → **reconciler 形态④**(本层 CLOSED + 芯片不在 LISTEN)relisten |
-| 恢复 | 对端 close 后 **~500ms** 内 :8888 重新接入(形态④ 去抖 2 轮 = 200ms + TIME_WAIT ~150ms + open/listen);新连接发完整 8 字节 TIME 包 → 设备落 **`wall clock updated`** |
-| 探针判定 | `OVERALL: queue=PASS listener=PASS`(双 PASS) |
-
-**探针口径更新**(`test/host/e02_probe.py`,本轮改):
-
-1. **模拟守规矩对端**:第 3 步预算耗尽后,探针**主动 close 自己的 socket**(配合设备 FIN)——
- 这是"可恢复边界"的显式模拟;**不配合**(内核存活、自动 ACK FIN、socket 不关)场景**不在探针判据内**,
- 其兜底 = 芯片 TIMEOUT/KeepAlive 拆僵尸(E13 定案 30~90s),探针头部已写明该定案口径。
-2. **3s 重试窗**:第二次连接由"单次 5s 尝试"改为 **3s 窗内 0.3s 节奏重试**——
- 恢复本身要经过 TIME_WAIT + 形态④ 去抖(合计 ~500ms 量级),单次即时尝试会把
- "正在恢复"误判成 FAIL;重试窗只放宽**时机**,不放宽"必须重新接入 + 对时落地"的判据。
-3. 顺序修正:close 移到第二次连接**之前**(旧脚本在第二次尝试之后才 close → 内核替挂起 socket
- ACK keepalive → 监听被合法占用 → 旧 FAIL 的根因,见 E13 第 5 条)。
-
-**结论**:E-02 三段收口齐备——① 挂起 recv 不冻结 netTask(阶段 1.5);② 应用可主动拆会话保监听
-(`net_drop_conn`,幂等、EOF 路径不得调);③ 复位机不搁浅(`net_poll` 步进 `ch395f_sock_reset_poll`)。
-芯片侧物理边界(无 RST、`FIN_WAIT_2` 长尾受限于对端)已登记 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-07 补充。
-
-#### E6 结果(2026-10-13 15:20–19:25,U15 插桩固件 Code=211692;用户裁决提前收线 ~4h,原规格 24h)
-
-- **配置**:`NET_U15_PROBE=1` 两段确认插桩(period=32、log_per_h=1),`U15 probe armed` ×2 确认
- (15:19/15:20 两次 boot);窗口内常态运行 + 每小时 `:7003×10`(gap=100ms)全过 + hour04 E1 压测
- (`:7003×50` gap=0 → 50/50、`:21×50` gap=100 → 50/50,拒连 0)。
-- **判据**:`U15 probe:` 命中 = **0**(含压测)→ 按 §E6 判据,U15 登记**观察项**,阶段 3 第 4 项
- (U15 内化)**不立项**;上层 32 轮盲收保留现状。
-- **零复位**:窗口内除 armed 两次 boot 外无启动锚点、无 Fault。
-- **附带观察(非 U15,登记备查)**:E1 `:21×50` 压测尾 25s 内 7 条 `[ERR][WD] task stall` tripwire
- (miss≈2.5s 级、缺失位在 0x80/0x14/0x4 间漂移),无复位自恢复、50/50 连接全过;同窗
- `sock2 TIMEOUT` + `[FTP] error reading next command` = 客户端放弃连接的正常形态。
- 若产品构建复现再查 FTP hammering 下的任务饿死。
-- **收尾**:宏已回 0、产品构建重编烧录回归;插桩代码留仓(宏=0 零成本),需要时重新武装。
diff --git a/docs/网络/Net层阻塞模型架构设计.md b/docs/网络/Net层阻塞模型架构设计.md
index 8846feed..bf1dd0ac 100644
--- a/docs/网络/Net层阻塞模型架构设计.md
+++ b/docs/网络/Net层阻塞模型架构设计.md
@@ -2,7 +2,8 @@
> **模块名称**:Net 层(`net_socket.c` / netTask)阻塞等待模型
> **文档定位**:**现状架构设计说明**——描述本层**当前如何组织等待与完成**、为什么这样设计、
-> 边界条件与已验证的性能特征。**不是历史记录**;演进过程与门禁实测数据作为溯源附于文末。
+> 边界条件与已验证的性能特征。**不是历史记录**:历史演进、门禁实测记录与修订日志
+> 一律不保留在文档内(需要时查 git 历史)。
> **状态**:设计已落地并全量台架验证(`v2-on-ui`)。
> **日期**:2026-09-29
> **相关文档**:
@@ -43,8 +44,8 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
| 区域 | 内容 |
|---|---|
| **左栏 · 调用方任务** | `net_recv` → 判"数据已在?" → **是**走快速路径;**否**(`timeout > 0`)登记 pending 后在自己上下文睡任务通知 |
-| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable + 全量刷新驱动状态缓存)→ 3 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 了结挂起 → `net_reconcile_tick` TRAP-NET-17 对账 + 监听重建唯一入口,N34 阶段 3 起原 `net_listener_pump` 已并入)→ 排空消息队列 |
-| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建;tripwire 60s 只留痕(N34 阶段 1.5 由 1s 看门狗降级) |
+| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable + 全量刷新驱动状态缓存)→ 3 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 了结挂起 → `net_reconcile_tick` TRAP-NET-17 对账 + 监听重建唯一入口)→ 排空消息队列 |
+| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建;tripwire 60s 只留痕 |
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
**关键性质**:
@@ -52,12 +53,10 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
- 等待期间 netTask **零额外占用**——`net_pending_tick` 只读标志与到期时间,
数据到达判据 `readable` 由主循环既有的 `net_poll()` 每轮刷新,**不引入额外 SPI 事务**;
- 调用方**在自己的上下文睡眠**(任务通知),与 LwIP 的消费者线程模型同构;
-- 公开 API 与调用契约**不变**——`net_recv` / `net_recv_to` 的签名、返回值、
- 超时语义均与改造前一致,既有调用方、测试与模拟器零改动。
- (**N34 阶段 1.5 更新**:本条描述 L3 改造当时口径;2026-10-13 起 `net_recv`
- 默认预算 30s 删除、改为无限阻塞,`net_recv_to` 成为唯一显式预算入口——
- 挂起模型与"等待不占 netTask"性质不变,变的是超时归属:见
- `docs/网络/N34-自愈审计.md` §5.3.5 与 BSD 指南 §3.5。)
+- 公开 API 与调用契约**稳定**——`net_recv`(默认无限阻塞)/ `net_recv_nb` +
+ `net_setblocking` / `net_recv_to`(唯一显式预算入口)的签名与返回值不变,
+ 既有调用方、测试与模拟器零改动;超时归属语义详见
+ `docs/网络/网络自愈架构设计.md` §4.5 与 `docs/网络/BSD_Socket_API_使用指南.md` §3.5。
> 图形由 `tools/arch_diagram/gen_net_pending_svg.py` 生成(改流程后重跑;见
> `docs/架构设计/图形绘制流程.md`)。
@@ -71,7 +70,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
| 项 | 设计 |
|---|---|
| 存储 | **独立静态数组** `s_net_pending[NET_MAX_SOCKETS]`(不置于 `net_sock_t` 内) |
-| 字段 | `active` / `infinite` / `tripwire_logged` / `buf` / `len` / `deadline` / `registered` / `caller`(`infinite`/`tripwire_logged` 为 N34 阶段 1.5 新增:0 预算 = 无限等待、tripwire 只留痕一次) |
+| 字段 | `active` / `infinite` / `tripwire_logged` / `buf` / `len` / `deadline` / `registered` / `caller`(`infinite`:0 预算 = 无限等待;`tripwire_logged`:tripwire 只留痕一次) |
| 容量 | 每 fd **至多一个**挂起请求 |
| 占用 | ≈24B/fd × 8 fd = **192B**(+4B 任务句柄),实测 SRAM +200B |
@@ -97,7 +96,7 @@ socket 生命周期解耦,与 `net_listener_retry_tick` 的重试表同构。
| # | 条件 | 动作 | 设计要点 |
|---|---|---|---|
| 1 | **数据到达** | SPI 读出 + 通知(结果 = 字节数) | 由 `net_poll` 维护的 `readable` 门控,**零额外 SPI**;仅 ESTABLISHED / UDP_OPEN 态可读;spurious readable 需清除 |
-| 2 | **到点** `now >= deadline`(仅显式预算登记;无限等待登记无 deadline——N34 阶段 1.5) | 通知 `NET_ERR_TIMEDOUT`(对外 -2 映射保持) | 状态无关;用有符号回绕安全差值,防 tick 回绕;这是 `net_recv_to` 调用方策略的正常兑现,不是伪造 |
+| 2 | **到点** `now >= deadline`(仅显式预算登记;无限等待登记无 deadline) | 通知 `NET_ERR_TIMEDOUT`(对外 -2 映射保持) | 状态无关;用有符号回绕安全差值,防 tick 回绕;这是 `net_recv_to` 调用方策略的正常兑现,不是伪造 |
| 3 | **CLOSED**(EOF/DISCONNECT) | 排空:有数据则读出;否则按 0(EOF) 完成 + `auto_relisten` + 通知 | CLOSE 先行完成挂起 = `net_close` 的 settle 钩子(排空→数据否则 CONNRESET) |
| 4 | **同 fd 后续操作** | 先结算挂起,再执行新操作 | per-fd 串行——冻结面从"全网"缩到"本 fd" |
| 5 | **网络重建**(`net_stack_bring_up`,芯片复位) | **取消 + 必须通知** | `net_init` 顶部 `net_pending_cancel_all(CONNRESET)`,先于芯片复位;**取消不通知 = 调用方永久挂死** |
@@ -105,11 +104,11 @@ socket 生命周期解耦,与 `net_listener_retry_tick` 的重试表同构。
**状态推断的克制**:CONNECTING / LISTENING 等中间态**保持等待**,**不做**状态推断式取消——
CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的等待。
-### 4.1 tripwire(原"看门狗",N34 阶段 1.5 降级)
+### 4.1 tripwire
挂起超 60s(`NET_RECV_TRIPWIRE_MS`)= "完成逻辑缺位"探针 → **只 `DBG_ERROR` 留痕一次,
-绝不强制了结、绝不伪造 TIMEDOUT 返回值**(旧设计"到点 +1s 强制了结"会掩盖完成路径
-缺陷,且与"无限等待"的新语义矛盾——伪造返回值违背调用方意图)。
+绝不强制了结、绝不伪造 TIMEDOUT 返回值**——强制了结会掩盖完成路径缺陷,伪造返回值
+违背调用方意图。
定位:留痕成本极低的开发期探针;唤醒责任在 ①数据 ②到点(仅显式预算)③断连 与
显式 cancel 路径(close / 网络重建 / 陈旧重同步),见 §4 五路。
@@ -125,9 +124,6 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
| 剩余预算循环 | 调用方保留"总预算 − 已耗时"循环骨架(`n == -2` 续轮,预算耗尽退出)——这是**公开契约要求**(30s 总超时语义 + 跨 TCP 分段累积),与本架构无关 |
| 超时精度 | 完成判定随主循环节拍(~1ms);主循环被长操作占用时,超时**最多顺延**相应时长(已知代价,见 §7) |
-> **已退役形态**:改造前调用方需自行实现"5ms 分片 + `vTaskDelay(1)` 让出"协议。
-> 该协议已随本架构落地而删除——分片是"执行者被占"前提下的妥协,pending 模型下不再需要。
-
---
## 6. 性能特征(已验证)
@@ -135,11 +131,9 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
| 指标 | 值 | 条件 |
|---|---|---|
| 队列响应性(挂起连接下) | **92.5ms** | 挂起对时客户端 + FTP banner 探测 |
-| 改造前同场景 | 28554ms | — |
| M3 静默档帧周期 | med 58.9 / p95 69.9 / max 72.1ms | 零尖峰 |
| M3 压力档帧周期 | med 60.1 / p95 66.4 / max 69.3ms | 零尖峰,p99 尾部 133.5→69.3ms |
| netTask 栈峰值 | 488B / 4096(余量 88%) | 零占用设计落点 |
-| RAM / Flash 增量 | +200B / +608B | 相对改造前 |
---
@@ -150,8 +144,8 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
| **SEND 路径未覆盖** | 发送仍为分块阻塞(1s/chunk stall) | 该期间队列仍被冻结;二期 pending 化解决 |
| **ACCEPT 路径未覆盖** | accept 仍用轮询计时 | 需 pending 化(二期) |
| **超时精度** | 随主循环节拍(~1ms),被长操作占用时顺延 | 一期文档化、不修;二期 SEND 化后该顺延面**收窄** |
-| **tripwire 未接自检** | 只 `DBG_ERROR` 留痕(N34 阶段 1.5 起不再强制了结),未写 selftest | 二期补齐 |
-| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟,N34 阶段 1.5) | 挂起表/tick/通知路径在模拟器上零覆盖,只能硬件验证;**监听生命周期 `net_listener.c` 亦为真机专属**(内部头只被 `net_socket.c` include,CMakeLists 不编译) |
+| **tripwire 未接自检** | 只 `DBG_ERROR` 留痕(不再强制了结),未写 selftest | 二期补齐 |
+| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟) | 挂起表/tick/通知路径在模拟器上零覆盖,只能硬件验证;**监听生命周期 `net_listener.c` 亦为真机专属**(内部头只被 `net_socket.c` include,CMakeLists 不编译) |
**二期扩展设计**(ACCEPT / SEND 的挂起化、门禁清单、改动量)见
`docs/任务规划/基线与待确认.md` §2.1。
@@ -167,63 +161,3 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
4. **不做状态推断式取消**——中间态(CONNECTING/LISTENING)合法迁移存在,
推断取消会误杀正常建链等待。
5. **数据到达不引入额外 SPI**——判据复用主循环既有 `net_poll` 维护的 `readable` 标志。
-
----
-
-## 附录 A:演进溯源
-
-> 本架构由 E-02 缺陷修复演进而来(明细见 `错误码传播审计.md` E-02)。**以下为历史信息,
-> 描述"如何演到这里",不作为当前设计依据。**
-
-**E-02 缺陷**:netTask 每次取一条消息处理到完成才取下一条;任一消费者发起的 30s 阻塞
-`net_recv` 期间队列整体停摆最长 30s(FTP 客户端空闲 >30s、对时客户端连上不发数据均可触发)。
-
-**两代形态**:
-
-| 代次 | 形态 | 状态 | 提交 |
-|---|---|---|---|
-| 一代 | 分片有界等待:`net_recv_to` + 5ms 片 + 30s 预算 + 片间 `vTaskDelay(1)` 让出 | 已退役 | `f8d6e90` / `4a1605b` |
-| 二代 | **pending 请求(本文档所述,现行)** | **一线现行** | `a45c0cb` |
-
-**实施与初始设计基线的偏差**(如实留档):
-
-| 基线条目 | 实施 | 说明 |
-|---|---|---|
-| pending 控制块 ≈20B/fd | ≈24B/fd × 8 = 192B | 实测 SRAM +200B,同量级 |
-| 控制块置于 `net_sock_t` 内 | **独立静态数组** | 见 §3.1 理由 |
-| 无数据 → 登记、跳过通知 | 哨兵 `NET_PENDING_REGISTERED`(0x70000000)→ `do_notify=0` | 逐字一致 |
-| 调用方改单次 `net_recv_to(…, 30000)` | **保留剩余预算循环** | 公开契约要求(见 §5) |
-
-**验证门禁逐条实测**(2026-09-22,设备 192.168.1.100):
-
-| # | 门禁 | 结果 |
-|---|---|---|
-| 1 | CH395F NET 层套件 | **PASS**:F1 组合 Phase 6/9/11(64B/1460B 回显 11/11、select 回显 11/11、断开重连、KeepAlive + auto-relisten);F2 组合 Phase 8-RECV(100KB 阻塞接收 50/50 块 + CRC32 整包一致 0x9A0E0C8C)。Phase 7/10 未跑(DONTWAIT 路径另由 M3 探针族 L5~L7 覆盖)。TC-NET-604 FAIL = 既有芯片侧问题(2026-09-23 结案非缺陷,判读教训见 `docs/问题记录/CH395F_Trap_Records.md` 台架纪律) |
-| 2 | M3 基线重锁 | **PASS**:见 §6 |
-| 3 | e02_probe A/B | **PASS 且优于分片方案**:队列响应性 92.5ms;30s 截止按节拍精确触发(30.1s);监听恢复 6.6s |
-| 4 | ftp_regression / E-06 假 425 | 随 F1/F2 + M3 压力档覆盖(FTP 半登录态即其场景) |
-| 5 | UI 远端推流压力 | **PASS**:`:7003` 100Hz 全帧 CRC 50/50,零尖峰 |
-| 6 | timeSync 三场景 | **PASS**:挂起 / 正常包;部分包由 Phase 8-RECV 跨段累积等效覆盖 |
-| 7 | 产品 `@test` + 全片回读 | **PASS**:Required 全中 / Forbidden 零命中;回读 `No difference found` |
-| 8 | RAM/栈前后对比 | **PASS**:见 §6 |
-
-**未执行 / 延后项**:
-
-- 未保留分片路径作影子对照 → 分片协议直接退役;对照由"旧镜像 A/B"承担,回退 = git revert
- (单提交、API 不变);
-- 探针族扩充(close/reconfig/EOF/disconnect-during-pending 专项)→ 二期;
-- 看门狗接 selftest 留痕 → 二期;
-- 模拟器差距 +1(`net_socket_sim.c` 无法建模挂起)→ 二期更新清单;
-- 台架期间另发现一次 49s 全系统日志静默(defaultTask/NAND syslog 排空停滞,net 栈照常工作)
- ——既有存储侧现象,与本层无关,见 `docs/任务规划/README.md` N-2。
-
-## 附录 B:修订记录
-
-| 日期 | 内容 |
-|---|---|
-| 2026-09-22 | 一期落地并全量台架验证(原《Net 层阻塞模型业界对照与演进方案》实施记录) |
-| 2026-09-26 | 调研文档退役,客观实施记录独立成文 |
-| 2026-09-29 | **改写为架构设计文档**:以"当前如何设计/为何如此"为主线重组织;历史演进与门禁实测降级为附录 A;迁入 `docs/网络/` |
-| 2026-10-03 | **网络重建失败恢复(P1-8 / A2)落地并真机验证**:首启组网失败 = 自检 `net init fail` + 30s 周期重试(1s 分片喂心跳,**不自驻留**);重建(reconfig)失败 = 自检 `net reinit fail` + 30s 延迟重触发事件位(tick 回绕安全),成功即清零;PHY 链路超时仍走既有 `comms fault` 降级(语义不变)。真机:开机 `net init: OK` / `PHY: 100M FULL` / `netTask: started`,无 `init FAIL` / `retry in 30s` 误触发(**正常路径零回归**);芯片级 `net_init` 失败分支 / 拔线负向场景待专项窗口(`待处理内容.md` N-16)。**执行判据见 `测试说明/实机测试实施指南.md` §7.1 / §10** |
-| 2026-10-12 | **N-32 监听生命周期模块化 + 单一所有者**:监听生命周期抽为 `net_listener.c`(`net_listener_try` / `net_listener_clear` / `net_listener_retry_tick` / `net_listener_pump` / `net_listener_settle`,真机专属、模拟器不编译);**重建动作收敛到唯一所有者 `net_listener_pump`**(100ms,去抖+冷却),对账 `net_reconcile_tick` 只剩①②;主循环自愈 tick 由 3 个增至 **4 个**。详见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 |
-| 2026-10-13 | **N34 阶段 3:对账与 pump 合并 + 忙等统一 + E-02 收口**(实施规格 `docs/网络/N34-自愈审计.md` §5.5):`net_listener_pump` 删除、并入 `net_reconcile_tick`(单一 100ms tick,形态①②③④⑤+U11,判据读驱动状态缓存,分级去抖 ②1/③④⑤2/①4 轮 + 1s 冷却)——**主循环自愈 tick 由 4 个回到 3 个**,单一重建所有者不变量不变(=形态③④);`net_listen_locked`/`net_connect_locked`/`net_close_locked` 的盲等改 `net_sock_reset_sync` 确定性排渣(GRACEFUL 100ms 底 / 600ms 封顶),close/connect 不再读清全局中断寄存器(F-NET-7);新增 `net_drop_conn()`(拆会话保监听,`NET_MSG_DROP_CONN`)+ `net_poll` 每轮步进 `ch395f_sock_reset_poll`。挂起 recv 模型本身不变(`net_drop_conn` 复用 `net_pending_settle_close` 先了结挂起)。台架回归全绿(Code=211348) |
diff --git a/docs/网络/网络自愈架构设计.md b/docs/网络/网络自愈架构设计.md
new file mode 100644
index 00000000..c9349ab7
--- /dev/null
+++ b/docs/网络/网络自愈架构设计.md
@@ -0,0 +1,289 @@
+# 网络自愈架构设计
+
+> **定位**:本文是 CH395F 网络自愈架构的**现状权威描述**——设计结构、原理、关键参数及其实证锚点。
+> 文中「收敛阶段 0~3」指 2026-10 自愈架构的分阶段实施(过程已归档,见 §9)。
+> 架构审计过程(T1~T4 三路审计与 39 条编号发现、E0~E14 实验方法与原始记录、分阶段实施清单)
+> 已完成使命并删除——过程全文在 git 历史(`git show 1a35b36^:docs/网络/N34-自愈审计/`,目录合并前
+> 7 份文档)。实证结论已沉淀进:`docs/问题记录/CH395F_Trap_Records.md`(TRAP-NET-01~21 与 B 系列)、
+> `docs/子系统设计/网络Socket监听生命周期设计说明.md`(D1~D10 与模块化边界)、`docs/芯片手册/CH395F.md`。
+> 仍被代码与文档引用的 12 条编号发现保留在 §7 速查表维持可寻址。
+
+## 1. 为什么是这个架构
+
+三个芯片事实决定架构形态(全部台架实证,见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-09/-17/-18/-20
+与 §5.2 锚点):
+
+1. **芯片永不自动回 LISTEN**——单连接复用模型(监听与数据通道复用同一 Socket),
+ "监听常驻"只能是软件承诺(I4)。
+2. **事件面不是可靠唯一信源**——旧"芯片丢中断"说法已被 E14 复核**推翻**(2026-10-14,
+ 48 轮直读 SINT 矩阵:芯片侧事件丢失 = 0,SINT 锁存可靠)。历史"丢失"全部是
+ **读清消费竞态**(台架 post-listen drain 吞掉即时到达的 CONNECT,drain-int 探针
+ 现场抓获 3/48;生产 B3 类游离全局读已清除)+ **门控观察伪象**(2/48,SINT 30ms 内
+ 即捕获)。但结论不变:事件可被其它合法读者消费,且状态面存在事件覆盖不了的幽灵
+ (E7b)→ 正确性必须靠回读芯片事实 + 有界超时兜底(I3)。
+3. **close 有沉降窗、RX 残渣有害**——带非空 RX 关闭会拆链停滞、吞下一会话 SYN 且自持
+ (E4 变体 C 0/20);沉降窗不定长(TRAP-NET-09/-20)→ 复用前清理必须内联进事务(I5)。
+
+主流对照(WIZnet ioLibrary / Zephyr / FreeRTOS+TCP / mbed / lwIP 移植)印证同一判断:
+没有任何主流方案维护第二份 socket 状态镜像、也没有"周期修正镜像"的对账;芯片怪癖一律
+**内联进事务、收在驱动层、以有界超时兜底**。本仓旧"五层自愈"补丁的结构性根源是三件事的
+组合:net 层持有损 8 态镜像并作为决策依据 + 状态迁移只靠中断事件 + 重建与事务脱钩。
+现架构(§2~§4)即按此收敛的结果。
+
+## 2. 设计原则(不变量)
+
+| # | 不变量 | 违反的后果(均实测) |
+|---|---|---|
+| I1 | **单一事实源**:芯片状态的权威副本 = 驱动每 Socket 缓存,唯一刷新点 = `net_poll` 每轮全量刷新;net 层 8 态 `net_sock_state_t` 只是"会话投影"(展示/门控),决策点读缓存或主动回读 | 有损镜像产生"镜像说 A、芯片是 B"窗口——旧五层补丁全在为这个窗口付税(F-MS-8) |
+| I2 | **单一重建所有者**:唯一周期性监听重建决策者 = `net_reconcile_tick` 形态③④;事件处理器与沉降登记**只置事实、不动作** | 多所有者竞争——"谁先看到 DISCONNECT 谁重建"→ 抢先 relisten 吞慢消费者的 EOF(N-32 根因、B10) |
+| I3 | **事件只负责提前唤醒,正确性靠回读 + 有界超时** | 纯事件驱动 → 事件被读清消费竞态吞掉后监听静默死亡(TRAP-NET-17;E14 复核:事件非芯片侧丢失,是被消费) |
+| I4 | **监听常驻 = 软件承诺**(`auto_relisten`),不是芯片属性 | 出向连接被误判孤儿 / 监听永久失效 |
+| I5 | **复用前清理内联进事务**:`sock_reset` = 排空 RX → CLOSE → 排空事件(quiescent)→ 等 byte0=00H | 带非空 RX 关闭 → 拆链停滞、SYN 被吞、级联自持(E4 变体 C 0/20) |
+| I6 | **分层**:驱动持事实与机械;net 层持策略与所有权;应用层零自愈责任 | 应用层重试/手动自愈 = 第 6/7 层(F-DRV-9,已退役) |
+| I7 | **全局中断寄存器唯一读者 = `net_poll`**(F-NET-7;测试/台架代码同样禁自行读取,等事件一律直读本 Socket SINT——用户裁定 2026-10-13) | B3 级事故:吞其它 Socket 的 CONNECT/DISCONNECT/RECV_OK 边沿事件 |
+| I8 | **不留未证明的复杂度**(TRAP-NET-20 铁律) | 正面执行:U15 内化不立项(E6 命中=0);TSL 变体只作待用补丁(E12) |
+
+**去抖 + 冷却**:失配连续 N 轮才动手(分级,见 §4.1)+ 动手后 1s 冷却。去抖与冷却本是
+"镜像世界"的自偿成本(F-MS-9)——驱动持事实后探针去抖已删(读缓存无瞬态噪声);
+reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要覆盖芯片 10ms 级可见窗(E2)。
+
+## 3. 架构总览
+
+### 3.1 分层职责
+
+| 层 | 持有什么 | 关键接口 |
+|---|---|---|
+| 驱动层 `Drivers/BSP/CH395F/ch395f.c/.h` | 每 Socket 状态缓存 + 类型化回读/谓词;每 Socket 事件读即清护栏;复合复位状态机;命令完成判据(状态回读);链路态缓存 | `ch395f_sock_state_cached` / `ch395f_sock_events` / `ch395f_sock_reset_begin` / `ch395f_sock_reset_poll` / `ch395f_cmd_done` / `ch395f_glob_event_feed` / `ch395f_link_up_cached` |
+| net 层 `net_socket.c` / `net_listener.c`(netTask 单所有者) | fd↔Socket 映射、消息队列串行化、L3 pending recv、事件分发与回调;会话投影与粘滞电平;单一 reconciler;监听生命周期(armed/D10/settle);同步复位包装 | `net_poll` / `net_reconcile_tick` / `net_listener_try` / `net_listener_settle` / `net_listener_retry_tick` / `net_sock_reset_sync` / `net_drop_conn` |
+| 应用/服务层(`ui_net_sink` / `iec104_serv` / `lftpd` / `time_sync`) | 只管 fd 与数据;放弃会话走 `net_drop_conn`;EOF 不得 `net_close` 监听 fd | — |
+
+### 3.2 主循环自愈三 tick
+
+| tick | 节奏 | 职责 |
+|---|---|---|
+| `net_poll` | 主循环每轮(≈1ms) | INT# 引脚 → 全局事件汇聚(唯一读者)→ SINT 分派 → **驱动缓存全量刷新** → 链路位喂驱动 → 步进 `ch395f_sock_reset_poll` |
+| `net_process_messages` | 每轮 | RPC 消息处理;开头每轮 `net_pending_tick`(挂起 recv 了结)+ `net_listener_retry_tick`(D10) |
+| `net_reconcile_tick` | 100ms | 唯一周期对账 + 重建(§4.1) |
+
+### 3.3 状态模型(会话投影)
+
+- `net_sock_state_t` 8 态 = **本层会话投影**,非芯片状态事实源(I1)。
+- `session_dead`:"会话已结束"的粘滞**电平**(N-32 P1 不变量)——消费者不再被抢先
+ relisten 饿死;relisten / 形态④ / listen / RESYNC 刻意不清它(契约注释在位)。
+- `lost_conn_seen`:M3 可疑电平(§4.1)。
+- `last_peer_port` + `released_abnormal`:幽灵甄别与异常释放簿记。
+
+### 3.4 全景图
+
+
+——七个叠加的芯片怪癖与现架构的拆法落点(证据链见陷阱记录与 §5.2)。
+
+## 4. 机制详解
+
+### 4.1 `net_reconcile_tick`——单一 100ms tick(唯一周期性自愈入口)
+
+判据读驱动缓存(零 SPI;例外见表注),统一 1s 冷却:
+
+| 形态 | 判据(本层 ↔ 芯片) | 动作 | 去抖 |
+|---|---|---|---|
+| ① | ESTABLISHED ↔ 芯片 CLOSED / TCP_CLOSED | 复用 `handle_disconnect_event`(置事实 + GRACEFUL 沉降) | **4 轮**(≈400ms,M3 让位窗) |
+| ② | LISTENING ↔ 芯片已握手(TCP=ESTABLISHED) | `GET_REMOT_IPP` 幽灵甄别 → 补 accept(`handle_connect_event`) | **1 轮**(E2:LISTEN 可见 ≤10ms 无长尾) |
+| ③ | 监听型 fd 而芯片不在 LISTEN | `net_listener_try` 重建(走 I2 唯一入口) | 2 轮 |
+| ④ | 本层 CLOSED(auto_relisten)而芯片**正在** LISTEN(U11) | 只重同步本层为 LISTENING,**零芯片命令** | 2 轮 |
+| ⑤ | ESTABLISHED ↔ 芯片已回 LISTEN(静默回收,F-NET-2) | RESYNC 语义只修本层(转登端口 + 取消挂起 recv),零命令 | 2 轮 |
+
+表注:形态② 命中时的对端端口回读是本 tick 唯一按需 SPI。
+
+- **M3 可疑门**(`handle_disconnect_event` ESTABLISHED 分支):芯片 `0x05/0x00`
+ (Socket OPEN 但 TCP 槽 0x00——活连接应为 0x04,逻辑矛盾态)且 `lost_conn_seen==0`
+ 且 `recv_bytes==0` 且**芯片 RX 有在途数据**(不可毁的消费者资产,与 closed-with-data
+ 救回同源)→ 不释放,置可疑电平,交形态① 电平二次确认;双锁存 = 强证据直接放行。
+ 风暴态免疫实证(E9 R3:预置残渣后 n30 0/50;`:21 @0ms×50` 50/50 零误伤——健康 FIN
+ 芯片为 0x07、静默回收为 0x00/0x00,均不命中判据)。
+- 沿革:形态③④ = 原 N-32 `net_listener_pump`(阶段 3 并入);形态⑤ = 原
+ `net_resync_listen` API 语义(阶段 1 删 API 并入)。
+
+### 4.2 监听生命周期
+
+- **`auto_relisten`**:`net_listen` 成功即置位;所有重建路径的门控标志(I4)。
+- **`net_listener_armed`**:唯一监听门控。⚠ **必须放行 ESTABLISHED**——它是单连接模型
+ 的"待 accept"电平;漏放行时 i104 的 200ms 轮询抢不过形态② 去抖而整场饿死
+ (E10 实测 18/18 FAIL → 修复后 18/18 PASS,127s→8.3s)。
+- **D10 `net_listener_retry_tick`**:30ms 间隔 / 2s 预算的非阻塞重试;L3 保险
+ (同 fd 有在途挂起 recv 则跳过);"预算耗尽"不是终态——reconciler 形态③④ 周期接管。
+- **消费者驱动即时重建 ×3**(accept CLOSED 分支 / recv EOF / pending③):与周期兜底
+ 构成双层——**不可删**(P2a 实测:删消费者驱动 → CLOSED 窗口拉长 → `[WD] task stall`)。
+- `net_listener_settle(fd, mode)`:登记"会话已结束,重建前需沉降/隔离"——**只置事实**,
+ 动作交形态③④ / `net_listener_try`(I2)。
+
+### 4.3 沉降与排渣隔离(TRAP-NET-18)
+
+- 两档:**GRACEFUL 100ms 底**(正常释放)/ **QUARANTINE 400ms 底**(异常释放:芯片
+ TIMEOUT、FTP ABORT);800ms 强制放行(必须 < D10 的 2s 时限)。
+- **隔离期持续排空 RX**:中止后残渣(实测 rxdrain=2048 = 芯片单 Socket RX 缓冲满容,
+ E4)会被下一条连接读走 → 隔离期边等沉降窗边排空。
+- **GRACEFUL 无条件化**(M1,F-NET-15):旧 `recv_bytes≠0` 门是背靠背失效循环的放大器
+ ——被陈旧 DISCONNECT 杀死的零数据会话免沉降 → 残渣杀下一条连接、自持循环;
+ 无条件化后 n30 8%→96.7%(E7b)。⚠ M1 必须与 M4-lite(删沉降段全局读即清)同批,
+ 否则沉降成为每次断连热路径后放大 F-NET-7 供给面(E7 实测教训)。
+
+### 4.4 驱动原语(阶段 2 落地,E2/E3/E4 定案形态)
+
+- **类型化回读 + 缓存**:`ch395f_sock_state_cached(sn)` / `ch395f_get_sock_state(sn)` /
+ `ch395f_sock_is_listening(sn)`;`open=0` 时 `tcp_state` 返回哨兵 `0xFF`
+ (byte0 门控 byte1 的收口);sn>7 边界返回 CLOSED+哨兵。唯一刷新点 = `net_poll`
+ 每轮全量(单次回读 18.1µs,E2)。
+- **事件护栏**:`ch395f_sock_events(sn)` 只读本 Socket SINT(读即清),永不触碰全局
+ 寄存器(I7)。测试/台架代码等事件同样直读 `ch395f_get_sock_int_status(sn)`——
+ 不用全局位图门控(门控观察分不清"芯片未锁存"与"全局位被读清",TRAP-NET-20
+ 观察混淆源已就此消除)。**listen/relisten 的排事件读清必须在 LISTEN 命令之前**
+ (E14:post-listen 读清会吞掉即时到达的新连接 CONNECT——生产与台架同源根除)。
+- **复合复位状态机**:`ch395f_sock_reset_begin(sn, mode)` / `ch395f_sock_reset_poll(sn)`
+ ——非阻塞、`net_poll` 每轮步进(同步包装 600ms 放弃后复位机也不搁浅)。
+ 序列(E4 定案):① 读空 RX(`CLEAR_RECV_BUF` 等价可用,**取读空**——字节数可观测
+ 可留痕,保 `docs/芯片手册/CH395F.md` §5.52 bit3=1"读出残留数据后再关闭"契约)
+ ② CLOSE_SOCKET ③ 排空本 Socket 事件(drain-until-quiescent:连续两轮无新位)
+ ④ 轮询 byte0=00H。**LISTEN 态 Socket 的 CLOSE 是二段**(E2 补充发现:一次 CLOSE
+ 只拆监听,byte0 恒留 05H 不自落)——状态机内置二段补发(只补发一次)。
+ GRACEFUL 100ms / QUARANTINE 400ms 底,600ms 封顶。
+- **命令完成判据**:`ch395f_cmd_done` = **状态回读**(open 完成→byte0=05H;listen
+ 完成→TCP=01H;close 完成→byte0=00H)——GET_CMD_STATUS 对这三条命令无判别力且陈旧
+ 错误码(0x17)跨命令持续存活(E3);GET_CMD_STATUS 仅保留内核前慢命令与"命令级被拒"
+ 检测(被拒码是新值覆盖残留,方向不受歧义影响)。
+- **链路态缓存**:`net_poll` 读全局后把 bit0~3 喂 `ch395f_glob_event_feed`;PHY_CHANGE
+ → 重读 PHY 更新 `link_up` 缓存 + 变化计数;reconciler 订阅留痕(运行期拔网线不再静默)。
+- net 层同步包装 `net_sock_reset_sync`(listen/connect open 失败重试、close 快路径)
+ ——忙等统一,close/connect 路径不再读清全局寄存器(I7)。
+
+### 4.5 recv 语义与 pending(阶段 1.5)
+
+- `net_recv()` 默认**无限阻塞**;`net_recv_nb()` + `net_setblocking(fd, on)`(模式是
+ socket 属性);`net_recv_to()` = 显式预算便捷入口(`timeout_ms=0` 仍拒 INVAL)。
+- 唤醒源 = 数据 / EOF / close / 网络重建 CONNRESET;会话死亡兜底 = 形态①(≈400ms)。
+- **L3 pending recv**:阻塞 recv 不独占 netTask(挂起登记 + 主循环节拍完成);
+ **tripwire**:挂起超 60s 只 `[ERR]` 留痕一次(完成逻辑缺位探针),不伪造 TIMEDOUT。
+- **文档契约**(嵌入式特有):IWDG `required_mask` 内任务**禁止无限阻塞 recv**——
+ 必须 `net_select` 超时或 `net_recv_to` 显式预算。
+- 上层 32 轮盲收(`ui_net_sink` / `iec104_serv`)保留现状(E6 裁决,§4.8)。
+
+### 4.6 `net_drop_conn`——应用主动拆会话保监听(E-02 收口)
+
+- 语义:`net_pending_settle_close` 先了结挂起 recv → `net_sock_reset_sync(QUARANTINE)`
+ → 本层收口 CLOSED(不清 fd)→ `auto_relisten` 即时 `net_listener_try`
+ (失败交 D10 + 形态④)。
+- **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调**
+ (`auto_relisten` 已接管,再调属重复拆)。
+- 芯片物理边界(E-02 台架):`close` **无 RST 语义**——对端配合(收到 FIN 后其内核
+ close):`TIME_WAIT` ~150ms → 形态④ relisten(恢复全程 ~500ms);对端内核存活但
+ 不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close**,兜底 = 芯片
+ TIMEOUT/KeepAlive 拆僵尸 30~90s(E13)——不是应用可加速的路径。
+
+### 4.7 KeepAlive(服务器侧,E11/E13 定案)
+
+- `handle_connect_event` LISTENING 分支开启,参数 **60s/5s/3**(规定位置见 `docs/芯片手册/CH395F.md` §5.56;
+ 参数须 500ms 倍数且 IDLE>INTVL,TRAP-NET-06)。
+- 实测判死 **≈30s**(早于手册公式 75s——芯片定时语义与公式不符,以实测为准;短参数
+ 6s/2s/2 → ≈5.1s 证明参数可按需缩放);静默但**存活**的对端不误杀(对端会 ACK 探测)。
+- 消费链路:`TIM_OUT` → `handle_timeout_event` QUARANTINE → relisten → 端口恢复。
+- **僵尸会话自愈窗**(E13):对端静默死亡(拔线 / PC 崩溃 / 进程被杀,无 FIN/RST)→
+ 芯片侧 ESTABLISHED 僵尸占住单连接监听——有在途 TX ≈30s(重传耗尽)/ 纯空闲
+ ≈75-90s(KeepAlive)自动清除;**无需冷启动**(板上 RSTI←NRST,烧录/MCU 复位即芯片
+ 硬件复位)。台架做连接类实验的控制手段 = 下一组开跑前静置 ≥90s 让僵尸清窗。
+
+### 4.8 U15 探针(观察项,宏=0)
+
+- `NET_U15_PROBE` 插桩留仓(产品构建编译消失):`net_poll` 每 32 轮对在用 fd 探测
+ `GET_RECV_LEN>0 && !readable`(两段确认),armed 日志 + 命中低频日志。
+- **E6 裁决(2026-10-13,~4h 常态 + 每小时 :7003×10 + E1 压测):命中=0 → 内化不
+ 立项**,上层盲收保留现状(I8);将来出现数据面失配再武装重裁。
+
+## 5. 关键参数与实证锚点
+
+### 5.1 参数表
+
+| 参数 | 值 | 依据锚点 |
+|---|---|---|
+| `NET_RECONCILE_PERIOD_MS` | 100ms | E2:每轮全量刷新成本 ≈72µs;pump 时代 50ms 实测把 uiTask 最坏单轮推到 1034ms(`net_listener.c` 注释留痕) |
+| 去抖(分级) | ②=1 轮 / ③④⑤=2 轮 / ①=4 轮 | E2:LISTEN 可见 ≤10ms 无长尾 → ② 可单轮;①=4 轮 ≈400ms = M3 让位窗 |
+| 冷却 | 1s | 防重建风暴(E8 风暴签名) |
+| GRACEFUL / QUARANTINE / 强制放行 | 100ms / 400ms / 800ms | TRAP-NET-18 二阶段实测;强制放行必须 < D10 2s |
+| D10 | 30ms / 2s | TRAP-NET-09/-20:沉降窗不定长;2s 是预算不是终态 |
+| `sock_reset_sync` 封顶 | 600ms | E2:LISTEN 态二段 CLOSE ~180ms 落定 + 余量 |
+| KeepAlive | 60s / 5s / 3 | E11:判死 ≈30.0s;T2 不误杀空闲会话 |
+| recv tripwire | 60s | 阶段 1.5(完成逻辑缺位探针,不改语义) |
+| 恢复窗(正常断连→监听可用) | 0.3~1s | E1 发现 A:释放只置事实 → 等周期 tick → SYN 静默丢 → 客户端 1s RTO → `:7003` 复连 p50≈1020ms |
+
+### 5.2 实验锚点速查(E 编号被代码与陷阱记录引用,结论一句话)
+
+| E | 结论 | 落点 |
+|---|---|---|
+| E0 | 常态 3.5h 对账零命中(冷路径);D10 逐轮留痕是主要日志噪声源 | reconciler 热路径成本可接受;重试日志只留首末 |
+| E1 | `:7003` 复连 50/50 p50≈1020ms(恢复窗 0.3~1s);背靠背 STOR 4/50 失效循环(发现 B) | P0 止血包;`n30_repro` 纳入常备回归 |
+| E5 | 静默回收形态 ~110 次连接零复现(代码缺口真实、触发罕见) | 阶段 1 顺带修(形态⑤),不单独立项 |
+| E7/E7b | M1+M2+M4-lite:n30 8%→96.7%(116/120);M1 不带 M4-lite 会放大 F-NET-7 | P0 转正(`9ac9879`);M1/M4-lite 同批纪律 |
+| E8 | 故障率 2%~54% 状态依赖(残渣存量);风暴三机制(F-NET-17) | M3/M5 设计 + "沉降让位救回"红线 |
+| E9 | M3 判据三轮迭代定案(资产门版);风暴免疫 + 零误伤 | §4.1 M3 门(`ae17d36`) |
+| E10 | 阶段 0/1 转正全绿;**armed 必须放行 ESTABLISHED**(i104 18/18,127s→8.3s) | §4.2 门控 |
+| E11 | 服务器侧 KeepAlive 真实可用;判死 ≈30.0s(手册公式不成立);静默存活不误杀 | §4.7 转正 |
+| E12 | TSL 规格偏离:460 轮/≈58MB 双向 SHA-256 无可复现危害、两臂无差异 | §6 红线 6;B 变体留作待用补丁 |
+| E13 | 风暴态根因 = 僵尸会话(确定性复现);RSTI←NRST 推翻"跨烧录存活";自愈链端到端有效 30~90s | §4.7;PASV 预清缺口由 `sock_reset` 收口 |
+| E2 | 单次回读 18.1µs;LISTEN 可见 ≤10ms 无长尾;LISTEN 态 CLOSE 二段(byte0 留 05H) | 缓存刷新策略;② 去抖 1 轮;`sock_reset` 二段补发 |
+| E3 | OPEN/LISTEN/CLOSE 命令状态无判别力 + 陈旧 0x17 跨命令存活;SET 类完全不更新 | `cmd_done` 判据 = 状态回读 |
+| E4 | 复位序列 A(排RX→CLOSE→排事件→等00H)20/20 vs C(什么都不做)0/20 级联自持;rxdrain=2048 | §4.4 序列定案;两模式都必须排 RX |
+| E6 | U15 命中=0(~4h 常态 + 压测;用户裁决提前收线) | §4.8 观察项 |
+| E-02 | `drop_conn` 时序定案:配合对端 TIME_WAIT ~150ms→形态④;不配合 FIN_WAIT_2 长尾 | §4.6 |
+| E14 | "丢中断"复核(2026-10-14,48 轮直读 SINT):芯片侧事件丢失 = 0;"丢失"全部 = drain 读清消费(3/48,drain-int 现场抓获)+ 门控竞态(2/48)+ 主机同步(1/48);幽灵态本轮 0/48 未复现 | §1 事实 2 改写;TRAP-NET-17/-20 归因更正(机制保留不变);生产 listen/relisten 排事件挪至 LISTEN 命令**前**(根除自伤窗) |
+
+## 6. 红线(明确不做的事)
+
+1. **不改 EXTI/ISR**:全局事件寄存器整体读即清,ISR 读寄存器会把事件从 netTask 抢走
+ ——正是 B3 事故形状。
+2. **不启用多连接模式(FUN_PARA bit1)**:不支持多监听(A′ 出局);监听常驻只能软件承诺。
+3. **不删消费者驱动即时重建 ×3**(P2a 实测删除 → `[WD] task stall`)。
+4. **不改单连接复用模型**:硬 TCP 栈原生主流形态(ioLibrary 无 accept、FreeRTOS reuse
+ 同形),不是对 BSD 的偏离。
+5. **SPI 分频 8、中断经 `net_poll` 集中读清**:PCB 约束 + I7(护栏是收窄读法,不是改路径)。
+6. **不改 TSL 单突发**:E12 实测 460 轮/≈58MB 无危害;B 变体(两段+TSL)留作待用补丁
+ (已证明无吞吐/正确性回退)。
+7. **不做盲收内化 / 不加 U15 对账**:E6 命中=0(I8)。
+8. **EOF 时不得 `net_close` 监听 fd**:模块约定铁律(会连监听一起毁掉)。
+9. **不把 DONE_CLOSE 当单次 CLOSE 的充分条件**:LISTEN 态 Socket 需二段(E2 补充发现)
+ ——走 `sock_reset`(内置补发)或保留 TRAP-NET-09 式重试。
+
+## 7. 编号发现速查(仍被代码/文档引用的 12 条)
+
+| 编号 | 一句话结论 | 收口落点 |
+|---|---|---|
+| F-NET-2 | "本层 ESTABLISHED 而芯片已回 LISTEN"无任何机制认领 | 对账形态⑤(阶段 1,`net_resync_listen` 已删) |
+| F-NET-3 | `net_relisten` / `net_resync_listen` / `net_can_accept` 是冗余或过期 API 面 | 全部删除(阶段 1) |
+| F-NET-4 | iec104 过期门控 → 丢 CONNECT 探针对 :2404 从不运行 | 门控统一 `net_listener_armed`(阶段 1) |
+| F-NET-7 | 全局寄存器读即清残留会搁浅其它 Socket 事件 | I7 + `sock_events` 护栏(阶段 2/3) |
+| F-NET-15 | GRACEFUL 沉降门 `recv_bytes≠0` = 背靠背失效循环放大器 | M1 无条件化(M2/M4-lite 同批,`9ac9879`) |
+| F-NET-17 | 风暴态三机制(沉降自再生锁存 / 泵与救回互抢 RX / 芯片 OPEN 卡死放大) | M3 可疑门 + `sock_reset` quiescent + 让位红线 |
+| F-DRV-1 | 同一芯片事实 7 处独立回读、三套去抖参数——单一事实源缺失是五层膨胀的地基 | 驱动缓存(I1,阶段 2) |
+| F-DRV-4 | "close+排空+等 CLOSED"两套同构变体参数漂移 | `ch395f_sock_reset` 统一 |
+| F-DRV-8 | 命令完成等待是阻塞原语(`HAL_Delay(20)` 忙等、4s 上限) | `cmd_done` 状态回读判据 + 非阻塞状态机 |
+| F-DRV-13 | 链路事件四个全局位读清后无人消费,运行期拔网线完全静默 | `glob_event_feed` + `link_up` 缓存(阶段 2/3) |
+| F-MS-8 | 芯片 11 态 → net 层 8 态有损投影,有损镜像必生决策窗口 | 镜像降级为会话投影(I1、§3.3) |
+| F-MS-9 | 去抖/冷却是镜像世界的自偿成本 | 探针去抖已删(读缓存);reconciler 保留分级去抖(让位窗需求) |
+
+## 8. 延后项
+
+1. **relisten 沉降段统一**:`net_relisten_locked` 隔离段仍是手拼序列(排空 RX + 主动
+ close + 读本 Socket SINT + 轮询状态 + 30ms 重试轮),未统一改走 `sock_reset` 原语。
+ 判定:它不是忙等、时序是生产已调优的(TRAP-NET-18),改造收益只是去重复代码——
+ 待下一次触碰该热路径的任务顺路处理(原语已具备承接条件:二段 CLOSE、quiescent
+ 排空、等级化最短等待)。
+2. **部分读→再查 recv_len 定向 TC**:旧"部分读归零"机理未证实(与手册 §5.34 及
+ TC-NET-201 相反),注释约束已降级为历史观察——以定向用例结案(陷阱记录「台架纪律」
+ 节已挂该待办)。
+3. **U15 观察项**:若将来出现数据面失配(RX>0 但 `!readable`),重新武装探针重裁(§4.8)。
+4. **链路事件业务消费**:缓存与留痕已就绪;PHY_CHANGE → 自动重配的触发待产品需求。
+
+## 9. 引用与归档约定
+
+- 本文引用格式:`docs/网络/网络自愈架构设计.md` §N.M(紧邻写法);E 编号引 §5.2
+ (如"§5.2 E12"),F 编号引 §7。
+- **勿引用已删除的过程章节**(T1~T4 审计表、实验方法与原始记录、分阶段勾选清单)。
+ 过程考古:`git show 1a35b36^:docs/网络/N34-自愈审计/`(目录合并前 7 份文档)或
+ `git log --oneline -- docs/网络/N34-自愈审计/`。
diff --git a/docs/问题记录/CH395F_Trap_Records.md b/docs/问题记录/CH395F_Trap_Records.md
index 8a9d89df..2dc50d98 100644
--- a/docs/问题记录/CH395F_Trap_Records.md
+++ b/docs/问题记录/CH395F_Trap_Records.md
@@ -28,10 +28,10 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
| TRAP-NET-14 | IP/端口字节序 | ✅ | IP 四字节数组=点分顺序;16 位端口=低字节在前,两套口径 |
| TRAP-NET-15 | FUN_PARA bit0 迷思 | ✅ | bit0 保留必须为 0(`0x08`);Socket 可用性取决于缓冲显式分配 |
| TRAP-NET-16 | 单次写超发送缓冲(写穿) | ✅ | 上限=本仓 2 块发送缓冲 1KB(`NET_SEND_CHUNK_MAX`),门控是 SENBUF_FREE |
-| TRAP-NET-17 | 快速复连丢中断 | ✅ | 芯片丢 CONNECT/DISCONNECT → 周期对账 `net_reconcile_tick`(reconciler 之源) |
+| TRAP-NET-17 | 快速复连事件面未收到 → 监听静默死亡 | ✅ | 周期对账 `net_reconcile_tick`(reconciler 之源);E14 复核:非芯片丢事件,是读清消费竞态 |
| TRAP-NET-18 | 上传中止后首笔复连被截断 | ✅ | 残渣投递给下一条连接 → 两级沉降/隔离 + 异常释放回 450 删截断文件 |
| TRAP-NET-19 | SET_MAC 顺序 | ✅ | §9.2.1:SET_MAC 必须在 `INIT_CH395` **之前**(INIT 从 EEPROM 加载) |
-| TRAP-NET-20 | FTP 数据连接三连故障 | ✅ | 三重定案:丢中断窗口探针+幽灵甄别 / closed-with-data / 背靠背小段毒化 TX |
+| TRAP-NET-20 | FTP 数据连接三连故障 | ✅ | 三重定案:状态回读补 accept + 幽灵甄别 / closed-with-data / 背靠背小段毒化 TX("丢中断"归因已被 E14 复核推翻) |
| TRAP-NET-21 | IP 显示段序与对端地址 | ✅ | `net_inet_ntoa` 段序 MSB 首;对端地址用 `net_getpeername`,勿用 `getsockname` |
---
@@ -106,12 +106,12 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
3. **反核实**:依据中断做结论前用 `GET_SOCKET_STATUS` 验证实际状态一致(负向事件与 ESTABLISHED 矛盾时以状态为准)。
- **注意**:B3 修复后预清只准读**本 Socket** SINT,不得再全量读 8 个(见第六章 B3)。
-## TRAP-NET-17 · 快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡
+## TRAP-NET-17 · 快速复连事件面未收到 → 监听静默死亡(E14 复核:非芯片丢事件)
-- **状态**:✅(周期对账 `net_reconcile_tick` 是 N34 reconciler 的前身;阶段 3 已并入单一 100ms tick)
-- **现象**:单连接监听 socket 快速复连后**永久失效**(后续 SYN 被拒或超时,10s 内不自恢复),芯片其余部分完全正常(其他监听端口照常工作)。两种表现:芯片完成握手但 CONNECT 中断未上报 → 控制块滞留 LISTENING;或 DISCONNECT 中断丢失 → 本层仍以为有客户端。两种情况芯片侧都占住单连接监听;应用侧连锁 `net_send` 被拒、读不到 EOF。
+- **状态**:✅(周期对账 `net_reconcile_tick` 是自愈架构 reconciler 的前身;收敛阶段 3 已并入单一 100ms tick)
+- **现象**:单连接监听 socket 快速复连后**永久失效**(后续 SYN 被拒或超时,10s 内不自恢复),芯片其余部分完全正常(其他监听端口照常工作)。两种表现:芯片完成握手但本层收不到 CONNECT 事件 → 控制块滞留 LISTENING;或 DISCONNECT 事件未收到 → 本层仍以为有客户端。两种情况芯片侧都占住单连接监听;应用侧连锁 `net_send` 被拒、读不到 EOF。
- **复现(可信判据,纯客户端;工具 `MDK-ARM/tcp_reconnect_probe.py`)**:待机 60s 后 `:7003` 300ms 快速复连 → 修复前首 2 次成功、之后永久拒连(2/10);FTP `:21` 100ms×50 → 第 29 轮起永久拒连。对照 `:21` 0ms/600ms 与 `:8888` 正常 → 触发条件 = 复连间隔 <~1s + 轻负载。
-- **机制(设备侧日志实证)**:① 芯片丢 CONNECT 或 DISCONNECT 中断;② 此后本层**收不到任何中断**,纯事件驱动无路可走;③ 故必须**周期回读芯片状态对账**。裸机芯片台架(剥离 OS/net/FTP)独立复现——芯片天性,不是固件 bug。
+- **机制(设备侧日志实证)**:① 本层收不到对应事件;② 此后本层**收不到任何中断**,纯事件驱动无路可走;③ 故必须**周期回读芯片状态对账**。**归因更正(E14 复核,2026-10-14,48 轮直读 SINT 矩阵)**:旧"芯片丢中断、芯片天性"结论已被推翻——SINT 可靠锁存事件,"丢失"实为**读清消费竞态**(台架 post-listen drain 吞掉即时到达的 CONNECT,drain-int 探针现场抓获 3/48)与**门控观察伪象**(2/48,SINT 30ms 内即捕获)。对账机制保留不变:事件可被其它合法读者消费,且状态面存在事件覆盖不了的幽灵态。
- **修复(`net_socket.c`)**:
1. **周期对账 `net_reconcile_tick()`**(与 pump 合并为单一 100ms tick):修正失同步形态——①本层 ESTABLISHED 而芯片已无连接→按断开处理;②本层 LISTENING 而芯片已握手→补 accept;③本层 LISTENING 而芯片监听消失→重建;④孤儿监听(B10)→重建。**去抖+冷却**:同一失配连续 2 次才动手,动手后冷却 1s。
2. `handle_disconnect_event` LISTENING 分支:回读芯片状态——已不可监听即重建。
@@ -123,11 +123,11 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
- **状态**:✅(回归 4 轮 11/11;全局寄存器读即清见第六章 B3)
- **现象**:同一会话内**第 2 次及以后**的数据连接(LIST/STOR/RETR)两类失败,概率随上一条数据连接的负载与间隔变化:① **吞数据**——新连接建立成功、服务端 `net_send` 返回成功,客户端 20s 收不到任何字节;② **425**——新连接被接受后 ~10-20ms 出现 DISCONNECT(`chip tcp=0x00`),accept 失败。
-- **定案实验(裸机芯片台架,剥离 OS/net/FTP)**:`test/board/ch395f_reuse_bench.c` 内核启动前直接调 `ch395f_*` 受控矩阵 + 主机侧 `test/host/ch395f_reuse_host.py`。**剥离后 bug 依旧复现** → 纯芯片操作时序问题。关键现场:`post-listen st=0x05/0x04`(listen 成功但 TCP 态停在旧连接 ESTABLISHED=幽灵态)、`unexpected int=0x10`(只等来旧连接滞后 DISCONNECT)、`no CONNECT within 10s`(芯片状态已 0x04 但中断永远没来)。
-- **芯片级规律**:CH395F 拆链收尾窗口**变长且不可观测**(状态显示 CLOSED 持续 1s+ 也不代表拆完,实测可 >2s 且时长不定)。窗口内重新 `open+listen` 三种形态:① 握手完成但 **CONNECT 中断被丢**(TRAP-NET-17 机制的窗口期形态)→ 吞数据;② 状态直接假报 0x04 幽灵态(回读对端端口=旧连接端口);③ 旧连接滞后 DISCONNECT 打到新监听 → relisten 毁掉新连接 → 425。
+- **定案实验(裸机芯片台架,剥离 OS/net/FTP)**:`test/board/ch395f_reuse_bench.c` 内核启动前直接调 `ch395f_*` 受控矩阵 + 主机侧 `test/host/ch395f_reuse_host.py`。**剥离后 bug 依旧复现** → 纯芯片操作时序问题。关键现场:`post-listen st=0x05/0x04`(listen 成功但 TCP 态停在旧连接 ESTABLISHED=幽灵态)、`unexpected int=0x10`(只等来旧连接滞后 DISCONNECT)、`no CONNECT within 10s`(芯片状态已 0x04 但事件始终未到等待环——E14 复核:事件已被 drain 读清消费,非芯片丢失)。
+- **芯片级规律**:CH395F 拆链收尾窗口**变长且不可观测**(状态显示 CLOSED 持续 1s+ 也不代表拆完,实测可 >2s 且时长不定)。窗口内重新 `open+listen` 三种形态:① 握手完成但 **CONNECT 事件未收到**(E14 复核:事件被台架 post-listen drain 或读清竞态吞掉,非芯片侧丢失)→ 吞数据;② 状态直接假报 0x04 幽灵态(回读对端端口=旧连接端口);③ 旧连接滞后 DISCONNECT 打到新监听 → relisten 毁掉新连接 → 425。
- **已证伪的修复**(受控实验全部无效,列出防重踩):close 后固定沉降 100~3000ms / Socket 轮换(跨 socket 毒化)/ listen 后 close+重试 / 缓冲重分配 / close 前 TX 排空延时 / 禁用对账 tick(对账不是元凶)。
- **定案一:双信源原则——中断只是快路径,状态寄存器才是真相**:
- 1. **accept 路径内联探针**(`net_lost_connect_probe`,`net_accept_locked` 每 100ms 挂探针):芯片已 ESTABLISHED 而 CONNECT 中断未到 → 连续 2 轮去抖后补 accept;
+ 1. **accept 路径内联探针**(`net_lost_connect_probe`,`net_accept_locked` 每 100ms 挂探针):芯片已 ESTABLISHED 而 CONNECT 事件未到 → 连续 2 轮去抖后补 accept;
2. **幽灵态甄别**:补 accept 前回读对端端口(`GET_REMOT_IPP`),**= 上一条连接端口(`last_peer_port`)→ 幽灵,不 accept**;
3. 对账②同步加幽灵甄别;`last_peer_port` 登记点:`handle_disconnect_event` / `handle_timeout_event` / `net_relisten_try`(清零 remote_port 前转登)/ `net_close_locked`(跨 memset 保留——幽灵属芯片 Socket 级,与 fd 复用无关)。
- **定案二:closed-with-data accept(425 真根因=accept 竞态自毁缓冲)**:`FUN_PARA` bit3 `SOCKET_CLOSE=1`(§5.52 ②)下产生 DISCONNECT 后芯片**不关 Socket、不清缓冲**,RX 缓冲里 STOR 载荷一个不少(探针实证 `recv_len=1027 re-read st=0x05/0x00`);那个 DISCONNECT 是**客户端正常 FIN**(ftplib `storbinary` 发完即关,局域网小文件 ~5ms)。旧 `net_accept_locked` 把 CLOSED 一律当"无连接" → `net_relisten_try`(close+open **清空芯片 RX 缓冲**)→ 数据被**我方亲手销毁** → 425。**修复**:CLOSED + auto_relisten + **芯片 RX 有数据**(`GET_RECV_LEN_SN`>0)→ 接受这条"已关但可读"连接(日志 `closed-with-data accept (drain pending)`),应用快路径排空,排空后 EOF 自然触发 relisten——对齐 §5.52 ② 契约。
@@ -153,8 +153,8 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
2. **C 模式真因**:`FUN_PARA` bit3 `SOCKET_CLOSE=1`(§5.52 ②)下产生 DISCONNECT 后**芯片不关 Socket、不清缓冲**,长期停在 `05H/00H`;此时直接 `open_socket` 必返 `0x20` FAIL——**把 Socket 拉回 CLOSED 的动作是 `close`**;
3. **做法**:直接 `ch395f_close_socket()`;`disconnect` 纯属多余且会**制造** DISCONNECT 事件(bit3=1 下必须被消费/预清,否则污染下一条连接,TRAP-NET-12/23 实证);
4. **观测口径**:§5.22 第 1 字节只有 `00H/05H`,第 2 字节(TCP 态)**仅在 byte0=OPEN 时有意义**——关闭后轮询**第 1 字节**等 `00H` 再 open,别拿 byte1 当"卡住"判据。
-- **边界(E-02 台架)**:`close` 无 RST 的适用条件——对端配合(收到 FIN 后其内核 close):芯片走 `TIME_WAIT` ~150ms → relisten → 新连接接入;**对端内核存活但不 close**:芯片停在 **`FIN_WAIT_2`(byte0=05H/byte1=06H)长尾**——没有 RST 就拆不掉,**监听恢复受限于对端 close**。不配合场景的兜底 = 芯片自身 TIMEOUT/KeepAlive 拆僵尸会话(判死窗 30~90s,`docs/网络/N34-自愈审计.md` §6 E13)——**不是**应用可加速的路径。
-- **应用侧出口 = `net_drop_conn(fd)`**(N34 阶段 3):netTask 侧走驱动 `sock_reset` QUARANTINE(FIN+排渣+排空事件+等 byte0=00H,400ms 底)+ 本层收口 CLOSED + `auto_relisten` 即时重监听;**保监听不毁**(单连接模型不得 `net_close` 监听 fd)。对端不 close 时复位机等在 `net_poll` 步进里(`ch395f_sock_reset_poll`),对端 FIN 到达即自动走完——**不搁浅**。
+- **边界(E-02 台架)**:`close` 无 RST 的适用条件——对端配合(收到 FIN 后其内核 close):芯片走 `TIME_WAIT` ~150ms → relisten → 新连接接入;**对端内核存活但不 close**:芯片停在 **`FIN_WAIT_2`(byte0=05H/byte1=06H)长尾**——没有 RST 就拆不掉,**监听恢复受限于对端 close**。不配合场景的兜底 = 芯片自身 TIMEOUT/KeepAlive 拆僵尸会话(判死窗 30~90s,`docs/网络/网络自愈架构设计.md` §5.2 E13)——**不是**应用可加速的路径。
+- **应用侧出口 = `net_drop_conn(fd)`**(自愈收敛阶段 3):netTask 侧走驱动 `sock_reset` QUARANTINE(FIN+排渣+排空事件+等 byte0=00H,400ms 底)+ 本层收口 CLOSED + `auto_relisten` 即时重监听;**保监听不毁**(单连接模型不得 `net_close` 监听 fd)。对端不 close 时复位机等在 `net_poll` 步进里(`ch395f_sock_reset_poll`),对端 FIN 到达即自动走完——**不搁浅**。
## TRAP-NET-09 · 沉降窗:PHY link up 后 / close 后不能立即 open
@@ -226,7 +226,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
## TRAP-NET-21 · IP 显示段序与对端地址(getsockname ≠ 对端)
-- **状态**:✅(N34 阶段 3 收尾修复)
+- **状态**:✅(自愈收敛阶段 3 收尾修复)
- **现象**:FTP 日志把 IP 打成 `100.1.168.192`(字节反转),且打印的是**本机** IP:21 而非客户端地址——排查连接来源时被误导。
- **根因(定案)**:① `net_inet_ntoa` 段序反转——`NET_NTOHL` 后主机数字的 **MSB 才是点分首段**,原实现低段在前;② lftpd 打印 `net_getsockname` = 本机监听地址(端口恒 21),对端地址应读 `net_getpeername`。PASV 227 消费点做了 `net_htonl` 再 MSB 拆,**双重反转恰好正确,勿误判**。
- **修复**:`net_inet_ntoa` 段序改 MSB 首(MCU+模拟器同步);新增 `net_getpeername`(读 CONNECT 事件缓存,零 SPI);lftpd 改打印**对端** IP:port(台架判据:日志 `connection received from 192.168.1.x:<高位端口>`)。
@@ -267,14 +267,14 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
| # | 严重度 | 位置 | 问题 | 状态 |
|---|---|---|---|---|
-| B2 | 中低 | `net_connect_locked` | `ch395f_tcp_connect()` 返回值曾被丢弃(§9.2.2 ⑧:SUCCESS 仅表示**命令**执行成功)。已修:非 SUCCESS 且**非 BUSY** 才判失败(`ERR_BUSY`=芯片仍在执行,当失败会误杀慢芯片);明确失败 → `[ERR] rejected by chip: 0xNN` + 按 §9.2.2 ⑧ 关 Socket 回滚 + 返 -1 | ⚠ 残余:`net_reconcile_tick` 仍只裁 ESTABLISHED/LISTENING,`CONNECTING` 丢中断无人收口(产品不走出向连接、无法实测,按"不加未证明复杂度"暂不动) |
-| B3 | 低→**结构性** | `net_listen_locked` + `net_relisten_locked`(后者是每次断连的热路径) | 对**全部 8 个 Socket** `GET_INT_STATUS_SN`(读即清)+ 无条件读清全局。机制:§5.15 全局中断寄存器**整体读即清、无按位清除手段**,而 `net_poll` 只在全局位置位时才读该 Socket 的 SINT(门控)→ 丢弃式读全局把别的在用 Socket 的门一并焊死;SINT 又**粘滞到被读** → 全量读走即**永久销毁**其 CONNECT/DISCONNECT/TIM_OUT/SENBUF_FREE(唯 RECV 是条件位可下轮补,TRAP-NET-08)。官方 SDK 21 个例程一律"全局位图门控 + 单 Socket 读 + 立刻转存",无一例全量读 8 个 SINT | ✅ 已修:两处收窄为**只清本 Socket SINT** + **删除丢弃式全局读清**(全局交 `net_poll` 集中读清=AGENTS 模块约定)。⚠ 残余:`net_connect_locked`/`net_close_locked`/`net_relisten_locked` 三处指定预清仍含无条件全局读清(会**搁浅**而非销毁其它 Socket 挂起事件)——它们是 TRAP-NET-12/23 的两级预清,宜与 B4 一并处置 |
+| B2 | 中低 | `net_connect_locked` | `ch395f_tcp_connect()` 返回值曾被丢弃(§9.2.2 ⑧:SUCCESS 仅表示**命令**执行成功)。已修:非 SUCCESS 且**非 BUSY** 才判失败(`ERR_BUSY`=芯片仍在执行,当失败会误杀慢芯片);明确失败 → `[ERR] rejected by chip: 0xNN` + 按 §9.2.2 ⑧ 关 Socket 回滚 + 返 -1 | ⚠ 残余:`net_reconcile_tick` 仍只裁 ESTABLISHED/LISTENING,`CONNECTING` 事件未收到无人收口(产品不走出向连接、无法实测,按"不加未证明复杂度"暂不动) |
+| B3 | 低→**结构性** | `net_listen_locked` + `net_relisten_locked`(后者是每次断连的热路径) | 对**全部 8 个 Socket** `GET_INT_STATUS_SN`(读即清)+ 无条件读清全局。机制:§5.15 全局中断寄存器**整体读即清、无按位清除手段**,而 `net_poll` 只在全局位置位时才读该 Socket 的 SINT(门控)→ 丢弃式读全局把别的在用 Socket 的门一并焊死;SINT 又**粘滞到被读** → 全量读走即**永久销毁**其 CONNECT/DISCONNECT/TIM_OUT/SENBUF_FREE(唯 RECV 是条件位可下轮补,TRAP-NET-08)。官方 SDK 21 个例程一律"全局位图门控 + 单 Socket 读 + 立刻转存",无一例全量读 8 个 SINT | ✅ 已修:两处收窄为**只清本 Socket SINT** + **删除丢弃式全局读清**(全局交 `net_poll` 集中读清=AGENTS 模块约定)。残余的三处指定预清无条件全局读清已由 2026-10-13 全局读收口全部删除(全库唯一读者=`net_poll`)。⚠ 2026-10-14 E14 复核追加:两处 listen 路径的排事件读清原在 LISTEN 命令**之后**——快速复连时新连接 CONNECT 落进该窗即被本方吞掉("数据被吞"的生产侧自伤机制,与 B3 同族),已挪至 LISTEN 命令**之前** |
| B4 | 低 | `net_socket.c` 全局中断分发 | 全局中断 bit0-3(`UNREACH`/`IP_CONFLI`/`PHY_CHANGE`/`DHCP`)被读清但从不消费;`ch395f_get_unreach_info()` 全仓零调用 → IP 冲突、ARP 不可达、链路抖动三类事件完全静默 | ⏳ 待修(可观测性缺口) |
| B6 | 低 | `net_socket.c` 3 处 | 状态判据只看 `byte1`、未按 §5.22 用 `byte0` 门控;reconcile 的 else-if 链里 `(00H, 陈旧 04H)` 会先命中"补 accept"而非"重建监听" | ⏳ 待修(TRAP-NET-07 实测 `byte0=00H` 时 `byte1` 读回 `0x00`,故为低概率) |
| B7 | 低 | `ch395f_test_task.c` Phase 5 | 单次写 4093B 超 1KB 发送缓冲与 2048 DMA 载荷 → 整块被丢;注释仍写旧上限。写路径金丝雀(A6)会打 `[ERR]` | ⏳ 待修(chunk 改 ≤1024 或分块) |
| B8 | 低 | `ch395f_test_task.c` Phase 4 | 把 sock6 缓冲重排到块 24-27,与 `net_init` 固定布局中 sock4 的 24-27 **交叠**(§5.45 禁止) | ⏳ 待修 |
| B9 | 低 | `ch395f_reuse_bench.c`、`net_socket.c` | bench 用 `get_phy_status()!=0` 判链路(01H=断开也成立,应 `!= CH395F_PHY_DISCONN`);`NET_PHY_CHECK_INTERVAL_MS` 死宏;无芯片版本门控(§5.16 <0x44 不支持 ALL 版、§5.52 bit3/4 需 ≥0x46,实测 0x4A 无影响) | ⏳ 待修(前两项死代码/卫生) |
-| B10 | **中** | `net_reconcile_tick` 认领范围 + `handle_disconnect_event` 释放分支 | **孤儿监听 → 端口永久拒连**:释放分支把状态置 CLOSED 等"应用重新监听",但 `ui_net_sink` 只在 `net_recv` 返 0(EOF)时才清 `g_client`,而 EOF 已被事件分支消费 → net 层等应用重建 / 应用等 net 层给 EOF / 对账只裁 ESTABLISHED·LISTENING 直接跳过 / 芯片不会自己回 LISTEN,**四方互等**。对账①③都要求本层处于 ESTABLISHED/LISTENING,**孤儿态恰好落在两类之外** | ✅ 已修:对账新增**形态④**认领孤儿(`in_use`+`state==CLOSED`+`auto_relisten`+`type==STREAM`+`local_port!=0`,芯片侧不在 LISTEN)→ `net_relisten_try`。⚠ `auto_relisten` 是决定性门:少了它出向连接会被误判孤儿 relisten 成监听器。⚠ ④ 的副作用:`net_recv` 的 EOF 是**边沿**——④抢先 relisten 会把慢消费者的 EOF 永久吞掉。**根治出口 = `net_drop_conn` + 生命周期设计**(N34 阶段 3 已落):原理与分阶段方案 → `docs/子系统设计/网络Socket监听生命周期设计说明.md` |
+| B10 | **中** | `net_reconcile_tick` 认领范围 + `handle_disconnect_event` 释放分支 | **孤儿监听 → 端口永久拒连**:释放分支把状态置 CLOSED 等"应用重新监听",但 `ui_net_sink` 只在 `net_recv` 返 0(EOF)时才清 `g_client`,而 EOF 已被事件分支消费 → net 层等应用重建 / 应用等 net 层给 EOF / 对账只裁 ESTABLISHED·LISTENING 直接跳过 / 芯片不会自己回 LISTEN,**四方互等**。对账①③都要求本层处于 ESTABLISHED/LISTENING,**孤儿态恰好落在两类之外** | ✅ 已修:对账新增**形态④**认领孤儿(`in_use`+`state==CLOSED`+`auto_relisten`+`type==STREAM`+`local_port!=0`,芯片侧不在 LISTEN)→ `net_relisten_try`。⚠ `auto_relisten` 是决定性门:少了它出向连接会被误判孤儿 relisten 成监听器。⚠ ④ 的副作用:`net_recv` 的 EOF 是**边沿**——④抢先 relisten 会把慢消费者的 EOF 永久吞掉。**根治出口 = `net_drop_conn` + 生命周期设计**(自愈收敛阶段 3 已落):原理与分阶段方案 → `docs/子系统设计/网络Socket监听生命周期设计说明.md` |
## 台架注入陷阱(方法论,持续补充)
@@ -308,9 +308,9 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
# 附录 A · 驱动关键点速查(细节以对应条目为准)
- **初始化顺序**:`SET_MAC → SET_IP/GWIP/MASK → INIT_CH395 → SET_PHY`(TRAP-NET-19;IP 族必须在 INIT 前——INIT 读取并锁定寄存器值;SET_PHY 在 INIT 后重建物理链路);`CMD_PING_ENABLE` 不需显式调用。
-- **SPI 通信**:每次事务 `ch395f_spi_begin()/end()` 包裹;分频 8(PCB 约束);大数据走 SPI2 DMA(`CH395F_SPI_DMA_BUF_SIZE`=2048+4=2052,`ch395f.h:148`;历史值 1500/4100 已作废)。⚠ **TSL 规格偏离(N34 E12 登记)**:手册 §7.4 要求缓存区命令"长度与数据之间"留 TSL(读 0.4us/写 0.2us),本仓两个缓存区函数把命令头与数据打包进**同一 DMA 突发**(间隔=0)属规格偏离;**实测未发现危害**(460 轮/≈58MB 双向 SHA-256 仅 1 次未复现异常,"两段+TSL"变体与现状无差异)→ 依 TRAP-NET-20 铁律暂不改产品路径,偏离与证据登记于此、变体留作待用补丁(完整 A/B 见 `docs/网络/N34-自愈审计.md` §6 E12)。
+- **SPI 通信**:每次事务 `ch395f_spi_begin()/end()` 包裹;分频 8(PCB 约束);大数据走 SPI2 DMA(`CH395F_SPI_DMA_BUF_SIZE`=2048+4=2052,`ch395f.h:148`;历史值 1500/4100 已作废)。⚠ **TSL 规格偏离(E12 登记)**:手册 §7.4 要求缓存区命令"长度与数据之间"留 TSL(读 0.4us/写 0.2us),本仓两个缓存区函数把命令头与数据打包进**同一 DMA 突发**(间隔=0)属规格偏离;**实测未发现危害**(460 轮/≈58MB 双向 SHA-256 仅 1 次未复现异常,"两段+TSL"变体与现状无差异)→ 依 TRAP-NET-20 铁律暂不改产品路径,偏离与证据登记于此、变体留作待用补丁(A/B 结论锚点 `docs/网络/网络自愈架构设计.md` §5.2 E12,过程全文见其 §9 归档)。
- **TCP 参数**(必须在 INIT_CH395 之前):重传 `SET_RETRAN_COUNT`(默认 12,最大 20)×`SET_RETRAN_PERIOD`(默认 500ms,最大 1000ms);KeepAlive 参数 500ms 倍数、IDLE>INTVL(TRAP-NET-06)。
-- **KeepAlive 定案链**:默认关闭 → 服务器侧应在 `SINT_STAT_CONNECT` 后开启(§5.56)→ 链路层死亡实测 +2s 即被重传(§5.17 8×200ms)覆盖、与 KeepAlive 无关 → **"链路在、对端静默"场景实测判死 ≈30s**(60s/5s/3,早于手册公式 75s,以实测为准;短参数 6s/2s/2 → ≈5.1s 证明参数生效;静默但存活对端不误杀)→ **已转正**:`handle_connect_event` LISTENING 分支开启 60s/5s/3;消费链路 TIM_OUT → `handle_timeout_event` QUARANTINE → relisten → 端口恢复(方法学与数据见 `docs/网络/N34-自愈审计.md` §6 E11/E13)。
+- **KeepAlive 定案链**:默认关闭 → 服务器侧应在 `SINT_STAT_CONNECT` 后开启(§5.56)→ 链路层死亡实测 +2s 即被重传(§5.17 8×200ms)覆盖、与 KeepAlive 无关 → **"链路在、对端静默"场景实测判死 ≈30s**(60s/5s/3,早于手册公式 75s,以实测为准;短参数 6s/2s/2 → ≈5.1s 证明参数生效;静默但存活对端不误杀)→ **已转正**:`handle_connect_event` LISTENING 分支开启 60s/5s/3;消费链路 TIM_OUT → `handle_timeout_event` QUARANTINE → relisten → 端口恢复(方法学与数据见 `docs/网络/网络自愈架构设计.md` §4.7 与 §5.2 E11/E13)。
- **Socket 缓冲**:默认只给 Socket 0~3;Socket 4~7 必须 `open_socket` 前显式分配(TRAP-NET-02);现行固定布局 6 块/Socket。
- **TCP 关闭重连**:直接 `ch395f_close_socket()`,不要先 `disconnect`;轮询 `GET_SOCKET_STATUS` **第 1 字节**等 `00H` 再 open;第 2 字节仅在 byte0=OPEN 时有意义(TRAP-NET-07)。
- **SOCK_TIMEOUT**:`SINT_STAT_SOCK_TIMEOUT` 不应视为致命——记录日志后继续操作(消费侧 QUARANTINE 见 TRAP-NET-18/B1 链)。
diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv
index c98e9881..dcd4dc35 100644
--- a/scripts/ram_baseline.tsv
+++ b/scripts/ram_baseline.tsv
@@ -1,3 +1,4 @@
date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi
2026-10-12 209492 18332 1104 154184 227824 21.7 97864 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;console_log.o=4120;ch395f.o=4106;default_task.o=2736;sys_log_mcu.o=1924
2026-10-13 211456 18760 1104 154232 230216 22.0 97912 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924
+2026-10-14 211448 18760 1104 154232 230208 22.0 97912 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924
diff --git a/simulator/sim/net_socket_sim.c b/simulator/sim/net_socket_sim.c
index 88a9bf97..ebe12428 100644
--- a/simulator/sim/net_socket_sim.c
+++ b/simulator/sim/net_socket_sim.c
@@ -11,7 +11,7 @@
* 设计要点:
* 1. fd 采用与 MCU 端一致的"控制块索引"模型(0 ~ NET_MAX_SOCKETS-1)
* 2. Winsock 句柄创建即置非阻塞(ioctlsocket FIONBIO),阻塞语义在包装层
- * 按控制块 blocking 属性模拟(N34 阶段 1.5:模式归使用者——blocking=1
+ * 按控制块 blocking 属性模拟(自愈收敛阶段 1.5:模式归使用者——blocking=1
* 时 recv 轮询等待(Sleep(1)),=0 或 NET_MSG_DONTWAIT 立即 WOULDBLOCK),
* 使 net_recv / net_recv_nb / net_recv_to / net_accept 的语义与 MCU 端一致
* 3. 代次号在模拟器上恒为 0(无"网络重建"概念),net_get_generation 恒返回 0
@@ -215,7 +215,7 @@ int net_socket(int domain, int type) {
s_socks[idx].type = (uint8_t)type;
s_socks[idx].state = NET_SOCK_STATE_CREATED;
s_socks[idx].ch395_sock = (uint8_t)idx;
- /* N34 阶段 1.5:阻塞模式默认 = 阻塞(与 MCU 端 net_socket 一致;
+ /* 自愈收敛阶段 1.5:阻塞模式默认 = 阻塞(与 MCU 端 net_socket 一致;
* Winsock 句柄仍恒非阻塞,阻塞语义由包装层按本属性模拟,见文件头要点 2) */
s_socks[idx].blocking = 1;
s_ws[idx] = ws;
@@ -334,7 +334,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
s_socks[nidx].type = (uint8_t)NET_SOCK_STREAM;
s_socks[nidx].state = NET_SOCK_STATE_ESTABLISHED;
s_socks[nidx].ch395_sock = (uint8_t)nidx;
- /* N34 阶段 1.5:accept 出的新连接同样默认阻塞模式(与 MCU 端一致) */
+ /* 自愈收敛阶段 1.5:accept 出的新连接同样默认阻塞模式(与 MCU 端一致) */
s_socks[nidx].blocking = 1;
s_ws[nidx] = cws;
@@ -342,7 +342,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return nidx;
}
-/* N34 阶段1(F-NET-4):net_can_accept 双端退役——监听 accept 门控统一用
+/* 自愈收敛阶段1(F-NET-4):net_can_accept 双端退役——监听 accept 门控统一用
* net_listener_armed()(本文件同名实现保留恒 1 语义),recv 门控 net_can_read() 保留。 */
/*
@@ -517,7 +517,7 @@ static int recv_wait(int sockfd, void *buf, int len, int has_budget, unsigned lo
}
/*
- * 函数功能:接收数据(TCP,**默认无限阻塞**——N34 阶段 1.5 与 MCU 端语义对齐)
+ * 函数功能:接收数据(TCP,**默认无限阻塞**——自愈收敛阶段 1.5 与 MCU 端语义对齐)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
@@ -542,12 +542,12 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
if ((flags & NET_MSG_DONTWAIT) || s_socks[sockfd].blocking == 0U) {
return recv_once(sockfd, buf, len);
}
- /* 阻塞:无限等待(无默认预算,N34 阶段 1.5) */
+ /* 阻塞:无限等待(无默认预算,自愈收敛阶段 1.5) */
return recv_wait(sockfd, buf, len, 0, 0ULL);
}
/*
- * 函数功能:接收数据(非阻塞具名入口,与 MCU 端同名 API 对齐——N34 阶段 1.5)
+ * 函数功能:接收数据(非阻塞具名入口,与 MCU 端同名 API 对齐——自愈收敛阶段 1.5)
* 入口参数:sockfd / buf / len - 同 net_recv
* 返回值:>0 字节数;0 = 对端关闭;-1 = 无数据(WOULDBLOCK) 或错误
* 限定条件:socket 已建立
@@ -566,7 +566,7 @@ int net_recv_nb(int sockfd, void *buf, int len) {
}
/*
- * 函数功能:接收数据(**显式预算**便捷入口,与 MCU 端同名 API 对齐——N34 阶段 1.5)
+ * 函数功能:接收数据(**显式预算**便捷入口,与 MCU 端同名 API 对齐——自愈收敛阶段 1.5)
* 入口参数:sockfd / buf / len - 同 net_recv
* timeout_ms - 本次等待预算(ms,必须 >0;0 = 无限等待语义
* 属 net_recv 的职责,此处拒 INVAL,与 MCU 端一致)
@@ -709,7 +709,7 @@ int net_close(int sockfd) {
}
/*
- * 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——N34 阶段 3,E-02 收口)
+ * 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——自愈收敛阶段 3,E-02 收口)
* 入口参数:sockfd - socket 描述符
* 出口参数:无
* 返回值:0 成功(含幂等:无活动会话),-1 失败
@@ -766,7 +766,7 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
}
/*
- * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(与 MCU 端同名 API 对齐——N34 阶段 1.5)
+ * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(与 MCU 端同名 API 对齐——自愈收敛阶段 1.5)
* 入口参数:sockfd - socket 描述符
* on - 1 = 阻塞(net_socket/net_accept 默认);0 = 非阻塞
* 返回值:0 成功;-1 失败(描述符无效/未占用)
@@ -836,7 +836,7 @@ uint32_t net_inet_addr(const char *cp) {
* 返回值:buf 指针(buf 为 NULL 时返回 NULL)
* 限定条件:buf 非空且长度足够
* 函数说明:NTOHL 后主机序数值的高字节是点分第一段(与 MCU 端同步修复,
- * N34 E13 §6:原低段在前显示成字节反转)
+ * E13 定案:原低段在前显示成字节反转)
*/
char *net_inet_ntoa(uint32_t addr, char *buf) {
uint32_t naddr = NET_NTOHL(addr);
@@ -949,7 +949,7 @@ int net_getsockname(int sockfd, struct net_sockaddr *addr, int *addrlen) {
}
/*
- * 函数功能:获取 Socket 对端地址(IP + Port)(与 MCU 端同名 API——N34 E13 §6)
+ * 函数功能:获取 Socket 对端地址(IP + Port)(与 MCU 端同名 API——E13 定案)
* 入口参数:sockfd - socket 描述符
* addr - 输出:对端地址
* addrlen - 输入输出:地址结构体长度
diff --git a/test/board/ch395f_reuse_bench.c b/test/board/ch395f_reuse_bench.c
index 86d0951e..18c53677 100644
--- a/test/board/ch395f_reuse_bench.c
+++ b/test/board/ch395f_reuse_bench.c
@@ -10,16 +10,16 @@
* 运行方法:在实验分支(如 n30-chip-bench)把本 .c 注册进 uvprojx Test 组、
* Src/main.c 的 USER CODE 2 区在 app_main_init() 之前调 ch395f_reuse_bench()
* (内核前裸机循环,永不返回,断电退出),烧录后跑主机脚本对照芯片日志。
- * 旧 N-30 定案矩阵(对照 vs "丢 CONNECT 捕获自 accept"修复组)保留在 s_vars[]/
+ * 旧 N-30 定案矩阵(对照 vs "CONNECT 事件未收到捕获自 accept"修复组)保留在 s_vars[]/
* bench_round(),由 s_bench_legacy=0 停泊不运行。
*
- * 2026-10-13 N34 阶段 2 前置实验(task-9):同一固件合并跑 E2/E3/E4——
+ * 2026-10-13 自愈收敛阶段 2 前置实验(task-9):同一固件合并跑 E2/E3/E4——
* E2 = GET_SOCKET_STATUS 单次 SPI 成本(DWT 周期计数)+ TCP_LISTEN 可见延迟矩阵;
* E3 = GET_CMD_STATUS 判别力矩阵(OPEN/LISTEN/CLOSE/SET_SOUR_PORT/SET_PROTO_TYPE,
* 含"命令未发时"对照读,复核 Trap 记录的 SET 类无判别力 + 陈旧 BUSY 残留);
* E4 = sock_reset 内部顺序 A/B/C 变体 ×10 轮(轮间换端口)+ 立即复连载荷完整性自证
* (配套 test/host/ch395f_reuse_host.py --mode e4)。
- * 判据与结果 → docs/网络/N34-自愈审计.md §6 E2/E3/E4 条目与实验记录节。
+ * 判据与结果锚点 → docs/网络/网络自愈架构设计.md §5.2 E2/E3/E4(过程全文已归档,见该文档 §9)。
*
* close_mode:0=主机先关(芯片等 DISCONNECT);1=芯片先关;2=无数据连上即关
* verify:0=无修复;1=listen 后确认 LISTEN 重试;2=缓冲重分配;
@@ -48,9 +48,9 @@ static const bench_var_t s_vars[] = {
{ 3, 0, 100, 0, 0 }, /* 0ms 对照 */
{ 3, 300, 100, 0, 0 }, /* 300ms 对照 */
{ 3, 800, 2000, 0, 0 }, /* 2KB 对照 */
- { 3, 0, 100, 0, 4 }, /* 0ms + 丢CONNECT捕获自accept */
- { 3, 300, 100, 0, 4 }, /* 300ms + 丢CONNECT捕获 */
- { 3, 800, 2000, 0, 4 }, /* 2KB + 丢CONNECT捕获 */
+ { 3, 0, 100, 0, 4 }, /* 0ms + 事件未收捕获自accept */
+ { 3, 300, 100, 0, 4 }, /* 300ms + 事件未收捕获 */
+ { 3, 800, 2000, 0, 4 }, /* 2KB + 事件未收捕获 */
};
#define BENCH_N_VARS (sizeof(s_vars) / sizeof(s_vars[0]))
@@ -71,9 +71,11 @@ static uint8_t s_rx_buf[2048];
static int bench_wait_sock_int(uint8_t sock, uint32_t timeout, uint8_t *out_bits) {
uint32_t t0 = HAL_GetTick();
while ((uint32_t)(HAL_GetTick() - t0) < timeout) {
- uint16_t glob = ch395f_get_glob_int_status_all();
- if ((glob & (1U << (sock + 4U))) != 0U) {
- uint8_t si = ch395f_get_sock_int_status(sock);
+ /* 直读本 Socket SINT(读即清)。不用全局寄存器门控:它读即清,未配对
+ * 读取会吞事件;且门控观察分不清"芯片未锁存"与"全局位被读清"
+ * (TRAP-NET-20 观察混淆源)。唯一读者 = net_poll(F-NET-7)。 */
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if (si != 0U) {
if (out_bits != 0) {
*out_bits = si;
}
@@ -168,6 +170,17 @@ static void bench_round(unsigned idx) {
DBG_ERROR("BENCH open fail 0x%02X", ost);
return;
}
+ /* 排事件在 LISTEN **之前**(对齐生产 listen 路径 E14 改法:post-listen 排事件
+ * 会吞掉快速到达的 CONNECT——上一版矩阵 3/48 被本探针现场抓获,现已根除该
+ * 自伤形态;本探针保留,drain 若仍见 CONNECT 即回归信号)。
+ * 逐 Socket SINT 读(读即清,不读全局——唯一读者 = net_poll,F-NET-7) */
+ for (int j = 0; j < 8; j++) {
+ uint8_t dj = ch395f_get_sock_int_status((uint8_t)j);
+ if (dj != 0U) {
+ DBG_INFO("BENCH drain-int sock%u=0x%02X t=%u", (unsigned)j,
+ dj, (unsigned)HAL_GetTick());
+ }
+ }
uint8_t lst = ch395f_tcp_listen(sock);
if (lst != CH395F_ERR_SUCCESS) {
DBG_ERROR("BENCH listen fail 0x%02X", lst);
@@ -182,11 +195,6 @@ static void bench_round(unsigned idx) {
ch395f_set_recv_buf(sock, recv_start, 4);
DBG_INFO("BENCH realloc done sock%u t=%u", (unsigned)sock, (unsigned)HAL_GetTick());
}
- /* 清残留中断(与生产 listen 后一致) */
- for (int j = 0; j < 8; j++) {
- (void)ch395f_get_sock_int_status((uint8_t)j);
- }
- (void)ch395f_get_glob_int_status_all();
bench_log_state("post-listen", sock);
/* 修复假设验证:listen 后 TCP 态必须真正 == 0x01(LISTEN);
@@ -199,17 +207,14 @@ static void bench_round(unsigned idx) {
uint8_t st[2] = { 0, 0 };
uint32_t tw = HAL_GetTick();
int connect_fired = 0;
- /* 等 TCP 进 LISTEN(幽灵消退)或 CONNECT 中断补发 */
+ /* 等 TCP 进 LISTEN(幽灵消退)或 CONNECT 中断补发(直读本 Socket SINT) */
while ((uint32_t)(HAL_GetTick() - tw) < budget) {
- uint16_t glob = ch395f_get_glob_int_status_all();
- if ((glob & (1U << (sock + 4U))) != 0U) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if ((si & CH395F_SINT_STAT_CONNECT) != 0U) {
- connect_fired = 1;
- DBG_INFO("BENCH verify CONNECT during ghost-wait att%d t=%u",
- att, (unsigned)HAL_GetTick());
- break;
- }
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if ((si & CH395F_SINT_STAT_CONNECT) != 0U) {
+ connect_fired = 1;
+ DBG_INFO("BENCH verify CONNECT during ghost-wait att%d t=%u",
+ att, (unsigned)HAL_GetTick());
+ break;
}
ch395f_get_socket_status(sock, st);
if (st[1] == 0x01U) {
@@ -235,13 +240,14 @@ static void bench_round(unsigned idx) {
for (int j = 0; j < 8; j++) {
(void)ch395f_get_sock_int_status((uint8_t)j);
}
- (void)ch395f_get_glob_int_status_all();
}
}
/* 2. 等 CONNECT(10s;幽灵等待期已见 CONNECT 则跳过)
* v6 关键探针:等待期每 500ms 读芯片状态;若已 ESTABLISHED(0x04)
- * 说明握手完成但 CONNECT 中断丢失 → 补做 accept(对账②模拟) */
+ * 说明握手完成但 CONNECT 事件未收到 → 补做 accept(对账②模拟)。
+ * E14 复核(2026-10-14,48 轮):事件从未被芯片丢失——"未收到"全部
+ * 是 drain 读清消费(见 drain-int)或竞态(见 CONNECT-in-SINT) */
int got = pre_connected;
uint32_t t0 = HAL_GetTick();
while (!got && (uint32_t)(HAL_GetTick() - t0) < 10000U) {
@@ -260,15 +266,40 @@ static void bench_round(unsigned idx) {
st[0], st[1], (unsigned)HAL_GetTick());
}
if (st[1] == 0x04U) {
- DBG_INFO("BENCH LOST-CONNECT caught: state ESTABLISHED, self-accept t=%u",
- (unsigned)HAL_GetTick());
+ /* 丢中断复核实验:宣布"未收到"之前补读 SINT ×3(10ms 间隔)——
+ * 若 CONNECT 在锁存,这是竞态不是丢失 */
+ int sint_seen = 0;
+ for (int rr = 0; rr < 3; rr++) {
+ if ((ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_CONNECT) != 0U) {
+ sint_seen = 1;
+ break;
+ }
+ HAL_Delay(10);
+ }
+ if (sint_seen != 0) {
+ DBG_INFO("BENCH CONNECT-in-SINT at state-04 (race, NOT lost) t=%u",
+ (unsigned)HAL_GetTick());
+ } else {
+ /* E14 定案:本形态若上方有 drain-int 0x08,事件是被 drain 消费
+ * (非芯片丢失);无 drain-int 才是真·未锁存(48 轮矩阵未出现) */
+ DBG_INFO("BENCH NO-CONNECT-EVENT at state-04, SINT empty x3 (check drain-int above) t=%u",
+ (unsigned)HAL_GetTick());
+ }
got = 1;
break;
}
}
}
if (!got) {
- DBG_ERROR("BENCH no CONNECT within 10s (host not synced?)");
+ /* 丢中断复核实验:对照变体 10s 内 SINT 每 10ms 读一次从未见 CONNECT——
+ * 此刻读状态+对端端口归类:0x04=芯片未锁存(实锤);0x01=主机未连(同步问题) */
+ uint8_t st[2] = { 0, 0 };
+ uint8_t rem[6] = { 0 };
+ ch395f_get_socket_status(sock, st);
+ ch395f_get_remot_ipp(sock, rem);
+ DBG_ERROR("BENCH no CONNECT within 10s: st=0x%02X/0x%02X rport=%u t=%u",
+ st[0], st[1], (unsigned)(rem[4] | (rem[5] << 8)),
+ (unsigned)HAL_GetTick());
ch395f_close_socket(sock);
HAL_Delay(v->delay_ms);
return;
@@ -311,11 +342,10 @@ static void bench_round(unsigned idx) {
}
}
DBG_INFO("BENCH tx %u written t=%u", (unsigned)v->tx_len, (unsigned)HAL_GetTick());
- /* 连接存活观察窗:500ms 内每 50ms 记状态+中断(抓"秒杀") */
+ /* 连接存活观察窗:500ms 内每 50ms 记状态+中断(抓"秒杀";直读 SINT) */
for (int k = 0; k < 10; k++) {
- uint16_t glob = ch395f_get_glob_int_status_all();
- if ((glob & (1U << (sock + 4U))) != 0U) {
- si = ch395f_get_sock_int_status(sock);
+ si = ch395f_get_sock_int_status(sock);
+ if (si != 0U) {
DBG_INFO("BENCH int during window=0x%02X t=%u", si, (unsigned)HAL_GetTick());
}
HAL_Delay(50);
@@ -345,13 +375,9 @@ static void bench_round(unsigned idx) {
ch395f_close_socket(sock);
}
- /* 5. 关闭后拆链观察:1s 内每 50ms 记状态 + 中断(TRAP-NET-12 滞后中断取证) */
+ /* 5. 关闭后拆链观察:1s 内每 50ms 记状态 + 中断(TRAP-NET-12 滞后中断取证;直读 SINT) */
for (int k = 0; k < 20; k++) {
- uint16_t glob = ch395f_get_glob_int_status_all();
- uint8_t late = 0;
- if ((glob & (1U << (sock + 4U))) != 0U) {
- late = ch395f_get_sock_int_status(sock);
- }
+ uint8_t late = ch395f_get_sock_int_status(sock);
if (late != 0U) {
DBG_INFO("BENCH late-int=0x%02X t=%u", late, (unsigned)HAL_GetTick());
}
@@ -366,7 +392,7 @@ static void bench_round(unsigned idx) {
}
/* =====================================================================
- * N34 阶段 2 前置实验区(2026-10-13):E2 / E3 / E4 一次固件合并跑
+ * 自愈收敛阶段 2 前置实验区(2026-10-13):E2 / E3 / E4 一次固件合并跑
* ===================================================================== */
/* 旧 N-30 定案矩阵停泊开关:0 = 不运行(保留代码备查,勿删) */
@@ -428,18 +454,15 @@ static int bench_wait_closed(uint8_t sock, uint32_t timeout_ms) {
}
}
-/* 排空本 Socket 事件:全局位图门控 + SINT 读(读即清),连续 2 轮(20ms 节奏)无新位即停;
- * 返回排空期间见过的 SINT 位 OR(E8 M5 drain-until-quiescent 的 bench 版) */
+/* 排空本 Socket 事件:直读 SINT(读即清),连续 2 轮(20ms 节奏)无新位即停;
+ * 返回排空期间见过的 SINT 位 OR(E8 M5 drain-until-quiescent 的 bench 版)。
+ * 不读全局寄存器——读即清,唯一读者 = net_poll(F-NET-7) */
static uint8_t bench_drain_events(uint8_t sock, uint32_t max_ms) {
uint32_t t0 = HAL_GetTick();
uint8_t seen = 0U;
int quiet = 0;
while ((uint32_t)(HAL_GetTick() - t0) < max_ms) {
- uint16_t glob = ch395f_get_glob_int_status_all();
- uint8_t si = 0U;
- if ((glob & (1U << (sock + 4U))) != 0U) {
- si = ch395f_get_sock_int_status(sock);
- }
+ uint8_t si = ch395f_get_sock_int_status(sock);
if (si != 0U) {
seen |= si;
quiet = 0;
@@ -635,15 +658,12 @@ static uint8_t e4_seed(unsigned rg) {
static int bench_e4_wait_death(uint8_t sock, uint32_t timeout_ms, uint8_t *out_bits) {
uint32_t t0 = HAL_GetTick();
while ((uint32_t)(HAL_GetTick() - t0) < timeout_ms) {
- uint16_t glob = ch395f_get_glob_int_status_all();
- if ((glob & (1U << (sock + 4U))) != 0U) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) {
- if (out_bits != 0) {
- *out_bits = si;
- }
- return 1;
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) {
+ if (out_bits != 0) {
+ *out_bits = si;
}
+ return 1;
}
HAL_Delay(10);
}
@@ -717,7 +737,7 @@ static void bench_e4_round(unsigned rg, uint8_t variant) {
HAL_Delay(200);
return;
}
- /* 消费循环:电平式 GET_RECV_LEN 收集(对丢 RECV 免疫),DISCONNECT/TIM_OUT 即止
+ /* 消费循环:电平式 GET_RECV_LEN 收集(对 RECV 事件未收到免疫),DISCONNECT/TIM_OUT 即止
* (= 生产 net 层释放语义;closed-with-data 救回不在本判据内,事后读空仅作取证) */
int death = ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) ? 1 : 0;
uint32_t total = 0U;
@@ -734,12 +754,9 @@ static void bench_e4_round(unsigned rg, uint8_t variant) {
total += (uint32_t)n;
rl = ch395f_get_recv_len(E4_SOCK);
}
- uint16_t glob = ch395f_get_glob_int_status_all();
- if ((glob & (1U << (E4_SOCK + 4U))) != 0U) {
- si = ch395f_get_sock_int_status(E4_SOCK);
- if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) {
- death = 1;
- }
+ si = ch395f_get_sock_int_status(E4_SOCK);
+ if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) {
+ death = 1;
}
if (!death) {
HAL_Delay(5);
@@ -796,7 +813,7 @@ static void bench_e4(void) {
}
void ch395f_reuse_bench(void) {
- DBG_INFO("BENCH ===== N34 E2/E3/E4 chip bench START (legacy matrix parked) =====");
+ DBG_INFO("BENCH ===== E2/E3/E4 chip bench START (legacy matrix parked) =====");
if (bench_chip_init() != 0) {
DBG_ERROR("BENCH init failed, giving up (normal boot continues)");
return;
@@ -806,12 +823,18 @@ void ch395f_reuse_bench(void) {
}
bench_dwt_enable();
if (s_bench_legacy != 0) {
- DBG_INFO("BENCH legacy N-30 matrix cycling %u variants", (unsigned)BENCH_N_VARS);
+ /* 丢中断复核实验(2026-10-14):固定 8 周期 × 6 变体后返回正常启动
+ * (主机脚本 ch395f_reuse_host.py --mode legacy --cycles 8 同步收工) */
+ DBG_INFO("BENCH legacy N-30 matrix: 8 cycles x %u variants", (unsigned)BENCH_N_VARS);
unsigned idx = 0;
- for (;;) {
- bench_round(idx);
- idx = (idx + 1U) % (unsigned)BENCH_N_VARS;
+ for (unsigned cyc = 0U; cyc < 8U; cyc++) {
+ for (unsigned iv = 0U; iv < (unsigned)BENCH_N_VARS; iv++) {
+ bench_round(idx);
+ idx = (idx + 1U) % (unsigned)BENCH_N_VARS;
+ }
}
+ DBG_INFO("BENCH legacy matrix DONE, normal boot continues");
+ return;
}
bench_e2();
bench_e3();
diff --git a/test/board/ch395f_test.h b/test/board/ch395f_test.h
index e93cd37d..1fbc9b45 100644
--- a/test/board/ch395f_test.h
+++ b/test/board/ch395f_test.h
@@ -67,7 +67,7 @@ extern "C" {
* E11 实证);勿与 Phase 6/9/10/13 混选(recv 互抢数据) */
/* ---- 链路时延基线(M1;分段/阈值见 docs/测试说明/链路延迟测试说明.md)---- */
//#define ENABLE_PHASE12_TESTS /* [硬] SPI 字节命令 / DMA 时延基线(L1/L2,无需 PC)—— M1 已完成,基线见说明文档 §6.1 */
-//#define ENABLE_PHASE15_TESTS /* [硬] N34 阶段 2 驱动原语:状态回读一致性/cmd_done/sock_reset 状态机跑通/链路缓存(无需 PC)——TC 详见 docs/测试说明/CH395F_Test_Guide.md 阶段 15;2026-10-13 台架 20/20 PASS */
+//#define ENABLE_PHASE15_TESTS /* [硬] 自愈收敛阶段 2 驱动原语:状态回读一致性/cmd_done/sock_reset 状态机跑通/链路缓存(无需 PC)——TC 详见 docs/测试说明/CH395F_Test_Guide.md 阶段 15;2026-10-13 台架 20/20 PASS */
/* ---- 链路时延(M2/M3;分段/阈值见 docs/测试说明/链路延迟测试说明.md)---- */
//#define ENABLE_PHASE13_TESTS /* [网] 芯片 + 队列时延(L3~L7/L10,需 PC peer)—— M2 已完成,基线见说明文档 §6.2 */
#define ENABLE_PHASE14_TESTS /* [网] 生产路径时延(L8~L11 + 模拟器 Δ)—— M3,需 peer --mode remo(:7003);
diff --git a/test/board/ch395f_test_task.c b/test/board/ch395f_test_task.c
index ab911f21..a6840778 100644
--- a/test/board/ch395f_test_task.c
+++ b/test/board/ch395f_test_task.c
@@ -77,12 +77,9 @@ static void test_parse_ip(const char *str, uint8_t *ip_arr)
static void phase1_run(void) {
uint8_t ip_info[20];
- /* ---- 前置清全局中断(读即清除) ----
- * net_stack_bring_up() 在 set_phy(100M_FULL) 强制重协商 + 等待链路建立
- * 期间,芯片会锁存 GINT_STAT_PHY_CHANGE(0x0004)标志;硬件层模式下无
- * netTask 轮询消费它,若不清除将导致 P1-06 的"原始状态 0x0000"检查误报。
- * 此处读取一次丢弃,保证 P1-06 反映的是测试期间的纯净状态。 */
- (void)ch395f_get_glob_int_status_all();
+ /* 全局中断寄存器读即清,唯一读者 = net_poll(F-NET-7;用户裁定 2026-10-13:
+ * 测试代码同样禁自行读取)。net_stack_bring_up() 的 set_phy 强制重协商会
+ * 锁存 GINT_STAT_PHY_CHANGE(0x0004)——由 P1-06 内部双读自清,不在此预清。 */
DBG_INFO("=== CH395F Phase 1 Tests ===");
DBG_INFO("--- 9 test items (P1-01 ~ P1-09) ---");
@@ -180,13 +177,15 @@ static void phase1_run(void) {
}
/* ---- P1-06: ch395f_get_glob_int_status_all()(16 位版) ----
- * 原理:读取全局中断状态寄存器(2 字节版本,含所有 Socket 中断位)。
- * 在无任何网络事件的空闲状态下,应为 0x0000。
- * 通过:返回 0x0000。
- * 失败:返回非 0x0000(有残留中断,如前序操作异常触发但未清除)。
- * 注意:此寄存器为读后清零,故先读 16 位版再读 1 字节版。
+ * 全库唯一允许 net_poll 之外读全局寄存器的位置——被测对象就是该 API 本身
+ * (硬件层模式独占上下文,无并发消费者)。读即清语义 → 先读一次自清
+ * (吸收 bring_up 锁存的 PHY_CHANGE),短暂延时后第二次读为断言值。
+ * 通过:第二次读返回 0x0000。
+ * 失败:第二次读非 0x0000(测试期间出现新事件)。
*/
{
+ (void)ch395f_get_glob_int_status_all(); /* 自清读(被测 API 自身,见上) */
+ HAL_Delay(10);
uint16_t gint_all = ch395f_get_glob_int_status_all();
DBG_INFO(" [P1-06] ch395f_get_glob_int_status_all()");
DBG_INFO(" expect: 0x0000");
@@ -330,54 +329,24 @@ static void phase2_tc201(void)
/* 步4:等待 CONNECT 中断
*
- * CH395F 的中断上报是【两级】结构:
- *
- * 第 1 级 —— 全局中断状态 GET_GLOB_INT_STATUS_ALL(16 位,读即清除)
- * 只回答"哪个 Socket 发生了事件",不回答具体是什么事件。
- * 16 位布局(见 ch395f.h GINT_STAT 宏定义):
- * bit0 UNREACH (0x0001) 目标不可达
- * bit1 IP_CONFLI (0x0002) IP 冲突
- * bit2 PHY_CHANGE (0x0004) 链路状态变化
- * bit3 DHCP (0x0008) DHCP 事件
- * bit4~7 Socket0~3 (0x0010/0x20/0x40/0x80)
- * bit8~11 Socket4~7 (0x0100~0x0800)
- * bit15 DHCPV6 (0x8000)
- *
- * 第 2 级 —— Socket 中断状态 CMD_GET_INT_STATUS_SN(8 位,读即清除)
- * 才包含具体事件类型:SENBUF_FREE / SEND_OK / RECV_OK /
- * CONNECT / DISCONNECT / DISCARD / SOCK_TIMEOUT。
- * 必须在第 1 级提示"本 Socket 有事件"后再读,否则读了也是空。
- *
- * 【掩码 (1U << (sock + 4)) 的由来】
- * 全局状态的 bit0~3 被 4 个全局事件占用,因此 Socket N 的事件标志
- * 固定从 bit4 开始排列 —— Socket N 对应第 (N + 4) 位:
- * sock=0 → 1<<4 = 0x0010(Socket0 事件位,宏已删 N34-0-5)
- * sock=1 → 1<<5 = 0x0020(Socket1 事件位,宏已删 N34-0-5)
- * ...
- * sock=7 → 1<<11 = 0x0800(CH395F_GINT_STAT_SOCK7)
- * `int_st & (1U << (sock + 4))` 非 0
- * ⇔ 芯片报告"Socket [sock] 有待处理事件",
- * 此时才值得再花一次 SPI 事务去读第 2 级确认具体事件。
+ * 直接读本 Socket 中断状态 CMD_GET_INT_STATUS_SN(8 位,读即清)——事件位:
+ * SENBUF_FREE / SEND_OK / RECV_OK / CONNECT / DISCONNECT / DISCARD / SOCK_TIMEOUT。
+ * 不用全局寄存器 GET_GLOB_INT_STATUS_ALL 做门控:它读即清,任何未配对 SINT
+ * 消费的读取都会吞掉事件通知;唯一读者 = net_poll(F-NET-7),测试代码同样
+ * 禁自行读取(用户裁定 2026-10-13)。测试知道自己操作哪个 Socket,直读
+ * SINT 即可——每轮一次 SPI 事务,成本与旧两级方案相同,且观察无门控混淆。
*/
tick = HAL_GetTick();
ok = 0;
while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) {
- /* 第 1 级:读全局状态。注意本次读取本身会清掉全部全局标志
- * 并拉高 INT#(读即清除),所以每轮循环都在重新采样。 */
- uint16_t int_st = ch395f_get_glob_int_status_all();
-
- /* 本 Socket 在全局寄存器中有事件挂起? */
- if (int_st & (1U << (sock + 4))) {
- /* 第 2 级:读本 Socket 的具体事件类型(同样读即清除) */
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & CH395F_SINT_STAT_CONNECT) {
- /* bit3 = 0x08:TCP 三次握手完成,进入 ESTABLISHED */
- ok = 1;
- break;
- }
- /* 是别的事件(理论上此阶段不应出现),已被读清、直接忽略,
- * 继续下一轮轮询等 CONNECT */
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if (si & CH395F_SINT_STAT_CONNECT) {
+ /* bit3 = 0x08:TCP 三次握手完成,进入 ESTABLISHED */
+ ok = 1;
+ break;
}
+ /* 是别的事件(理论上此阶段不应出现),已被读清、直接忽略,
+ * 继续下一轮轮询等 CONNECT */
HAL_Delay(10); /* 10ms 轮询周期,直至 CONNECT 或超时退出 */
}
TEST_CHECK(ok, "TC201 step4 connected");
@@ -392,13 +361,9 @@ static void phase2_tc201(void)
tick = HAL_GetTick();
ok = 0;
while (HAL_GetTick() - tick < 2000) {
- uint16_t int_st = ch395f_get_glob_int_status_all();
- if (int_st & (1U << (sock + 4))) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & CH395F_SINT_STAT_SENBUF_FREE) {
- ok = 1;
- break;
- }
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) {
+ ok = 1;
+ break;
}
HAL_Delay(10);
}
@@ -461,16 +426,12 @@ static void phase2_tc201(void)
}
/* ---- ② 连接异常检查(还没等到数据才走到这里)----
- * 两级中断:全局状态确认本 Socket 有事件 → 再读具体事件类型。
- * 注意先查数据再查断开:断开事件可能和最后一批数据同时到达,
- * 数据优先保证"临终回显"不被漏掉。 */
- uint16_t int_st = ch395f_get_glob_int_status_all();
- if (int_st & (1U << (sock + 4))) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) {
- break; /* 对端断开 / 超时:继续等也不会有回显了。
- ok 保持 0,跳出后由下方统一判 FAIL */
- }
+ * 直读本 Socket SINT(读即清)。先查数据再查断开:断开事件可能和
+ * 最后一批数据同时到达,数据优先保证"临终回显"不被漏掉。 */
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if (si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) {
+ break; /* 对端断开 / 超时:继续等也不会有回显了。
+ ok 保持 0,跳出后由下方统一判 FAIL */
}
/* 每 50ms 一轮。若本轮只读了部分数据(rlen > 300 的极端情况),
@@ -534,11 +495,7 @@ static void phase2_tc202_once(uint8_t sock, uint16_t local_port)
tick = HAL_GetTick();
ok = 0;
while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) {
- uint16_t int_st = ch395f_get_glob_int_status_all();
- if (int_st & (1U << (sock + 4))) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & CH395F_SINT_STAT_CONNECT) { ok = 1; break; }
- }
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_CONNECT) { ok = 1; break; }
HAL_Delay(10);
}
TEST_CHECK(ok, "TC202 step3 connected");
@@ -550,11 +507,7 @@ static void phase2_tc202_once(uint8_t sock, uint16_t local_port)
tick = HAL_GetTick();
ok = 0;
while (HAL_GetTick() - tick < 2000) {
- uint16_t int_st = ch395f_get_glob_int_status_all();
- if (int_st & (1U << (sock + 4))) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & CH395F_SINT_STAT_SENBUF_FREE) { ok = 1; break; }
- }
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) { ok = 1; break; }
HAL_Delay(10);
}
TEST_CHECK(ok, "TC202 step4 sendbuf_free");
@@ -578,11 +531,8 @@ static void phase2_tc202_once(uint8_t sock, uint16_t local_port)
ok = 1;
break;
}
- uint16_t int_st = ch395f_get_glob_int_status_all();
- if (int_st & (1U << (sock + 4))) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) break;
- }
+ if (ch395f_get_sock_int_status(sock) &
+ (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) break;
HAL_Delay(50);
}
if (!ok) TEST_CHECK(0, "TC202 step5 echo rcvd");
@@ -689,21 +639,18 @@ static uint8_t tc204_session(uint8_t sock, uint16_t lport, uint8_t mode, uint8_t
return 0;
}
- /* 等 CONNECT 中断(期间其它中断现场打印 = 残留事件取证点) */
+ /* 等 CONNECT 中断(期间其它中断现场打印 = 残留事件取证点;直读本 Socket SINT) */
tick = HAL_GetTick();
while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) {
- uint16_t gl = ch395f_get_glob_int_status_all();
- if ((gl & (1U << (sock + 4))) != 0U) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if ((si & CH395F_SINT_STAT_CONNECT) != 0U) {
- ok = 1;
- break;
- }
- if (si != 0U) {
- DBG_INFO("TC204 m%u r%u stale-int=0x%02X at +%ums",
- (unsigned)mode, (unsigned)round, si,
- (unsigned)(HAL_GetTick() - tick));
- }
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if ((si & CH395F_SINT_STAT_CONNECT) != 0U) {
+ ok = 1;
+ break;
+ }
+ if (si != 0U) {
+ DBG_INFO("TC204 m%u r%u stale-int=0x%02X at +%ums",
+ (unsigned)mode, (unsigned)round, si,
+ (unsigned)(HAL_GetTick() - tick));
}
HAL_Delay(10);
}
@@ -720,13 +667,9 @@ static uint8_t tc204_session(uint8_t sock, uint16_t lport, uint8_t mode, uint8_t
tick = HAL_GetTick();
ok = 0;
while (HAL_GetTick() - tick < 2000) {
- uint16_t gl = ch395f_get_glob_int_status_all();
- if ((gl & (1U << (sock + 4))) != 0U) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if ((si & CH395F_SINT_STAT_SENBUF_FREE) != 0U) {
- ok = 1;
- break;
- }
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) {
+ ok = 1;
+ break;
}
HAL_Delay(10);
}
@@ -917,7 +860,7 @@ static void phase2_tc204(void)
*
* 已知陷阱(TRAP-NET-12):上一会话 close 的 DISCONNECT 会滞后锁存在
* SINT_SN(wait-CLOSED 的 STATUS 查询不清中断)。防御双保险:
- * ① 步 2 前预清两级中断;
+ * ① 步 2 前预清本 Socket SINT;
* ② 判负向后用 GET_SOCKET_STATUS 反核实,ESTABLISHED 即改判环境错误。
*/
static void phase2_tc203(void)
@@ -946,8 +889,8 @@ static void phase2_tc203(void)
/* 预清陈旧事件(TRAP-NET-12):上一会话 close 的 DISCONNECT 可能滞后锁存
* 在 SINT_SN 中(wait-CLOSED 轮询的 STATUS 是纯查询、不清中断),
- * 若不清除,本用例首次读中断会误吃旧事件导致负向误判。 */
- (void)ch395f_get_glob_int_status_all();
+ * 若不清除,本用例首次读中断会误吃旧事件导致负向误判。
+ * 只清本 Socket SINT——全局寄存器读即清,唯一读者 = net_poll(F-NET-7)。 */
(void)ch395f_get_sock_int_status(sock);
/* 步2:OPEN + CONNECT。命令为异步下发,此时均应立即返回成功,
@@ -960,8 +903,9 @@ static void phase2_tc203(void)
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC203 step2 connect");
if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }
- /* 步3:30s 两级中断轮询,等"连不上"的证据。
- * 先扫全局 UNREACH 位作备注,再查本 socket 位取具体事件。
+ /* 步3:30s 直读本 Socket SINT 轮询(读即清),等"连不上"的证据。
+ * (旧版先扫全局 UNREACH 位作备注——已删:全局寄存器唯一读者 = net_poll,
+ * 该备注本就不影响判定。)
*
* 实测行为(2026-08 上板):PC 端口关闭时 SYN 被 RST 拒绝,
* 芯片以【毫秒级 SOCK_TIMEOUT】上报,并非等待 30s 静默重传耗尽;
@@ -973,23 +917,15 @@ static void phase2_tc203(void)
ev = 0;
neg_si = 0;
while (HAL_GetTick() - tick < PHASE2_TC203_TIMEOUT_MS) {
- uint16_t int_st = ch395f_get_glob_int_status_all();
-
- if (int_st & CH395F_GINT_STAT_UNREACH) {
- DBG_INFO(" global UNREACH: ARP fail (peer offline)");
+ uint8_t si = ch395f_get_sock_int_status(sock);
+ if (si & (CH395F_SINT_STAT_SOCK_TIMEOUT | CH395F_SINT_STAT_DISCONNECT)) {
+ neg_si = si; /* 记下事件位,跳出后再细分原因 */
+ ev = 1;
+ break;
}
-
- if (int_st & (1U << (sock + 4))) {
- uint8_t si = ch395f_get_sock_int_status(sock);
- if (si & (CH395F_SINT_STAT_SOCK_TIMEOUT | CH395F_SINT_STAT_DISCONNECT)) {
- neg_si = si; /* 记下事件位,跳出后再细分原因 */
- ev = 1;
- break;
- }
- if (si & CH395F_SINT_STAT_CONNECT) {
- ev = 2; /* 连上了 = PC 服务没关,属环境错误而非芯片故障 */
- break;
- }
+ if (si & CH395F_SINT_STAT_CONNECT) {
+ ev = 2; /* 连上了 = PC 服务没关,属环境错误而非芯片故障 */
+ break;
}
HAL_Delay(10);
}
@@ -1173,11 +1109,8 @@ static void phase3_run(void) {
uint32_t sw = HAL_GetTick();
while (HAL_GetTick() - sw < 2000) {
- uint16_t gs = ch395f_get_glob_int_status_all();
- if (gs & (1U << (sock + 4))) {
- if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE)
- break;
- }
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE)
+ break;
HAL_Delay(10);
}
echo_ok = 1;
@@ -1247,7 +1180,8 @@ static void phase4_run(void) {
uint32_t tick = HAL_GetTick();
uint8_t dhcp_ok = 0;
while (HAL_GetTick() - tick < 30000) {
- ch395f_get_glob_int_status_all();
+ /* DHCP 状态寄存器是真相(纯查询);不读全局寄存器——读即清,
+ * 唯一读者 = net_poll(F-NET-7),测试代码同样禁自行读取 */
if (ch395f_get_dhcp_status() == 0x00) { dhcp_ok = 1; break; }
HAL_Delay(500);
}
@@ -1310,10 +1244,7 @@ static void phase4_run(void) {
ch395f_write_send_buf(anno_sock, ip_info, 4);
uint32_t sw = HAL_GetTick();
while (HAL_GetTick() - sw < 2000) {
- uint16_t gs = ch395f_get_glob_int_status_all();
- if (gs & (1U << (anno_sock + 4))) {
- if (ch395f_get_sock_int_status(anno_sock) & CH395F_SINT_STAT_SENBUF_FREE) break;
- }
+ if (ch395f_get_sock_int_status(anno_sock) & CH395F_SINT_STAT_SENBUF_FREE) break;
HAL_Delay(10);
}
TEST_CHECK(1, "DHCP announcement sent");
@@ -1324,7 +1255,6 @@ static void phase4_run(void) {
int32_t hello_ok = 0;
uint32_t wait = HAL_GetTick();
while (HAL_GetTick() - wait < PHASE4_HELLO_WAIT_MS) {
- ch395f_get_glob_int_status_all();
uint16_t rlen = ch395f_get_recv_len(anno_sock);
/* ⚠ 同 Phase 3:夹到本地缓冲容量,防栈溢出(2026-10-11 审计修正) */
if (rlen > (uint16_t)sizeof(rx_buf)) {
@@ -1466,9 +1396,7 @@ static void phase5_run(void) {
t0 = HAL_GetTick();
while (HAL_GetTick() - t0 < PHASE5_CONN_TIMEOUT_MS) {
- uint16_t gs = ch395f_get_glob_int_status_all();
- if ((gs & (1U << (sock + 4))) &&
- (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_CONNECT)) {
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_CONNECT) {
conn_ok = 1;
break;
}
@@ -1523,13 +1451,10 @@ static void phase5_run(void) {
tx_sw = HAL_GetTick(); /* 记录等待起点(供超时判定) */
} else {
/* ---- 等待上一次写入被芯片搬走(SENBUF_FREE) ----
- * 两级中断轮询(DP-02):先读全局中断状态确认本 socket 有事件,
- * 再读该 socket 的中断状态确认具体为 SENBUF_FREE。
- * 读全局中断状态即清除中断标志(读即清)。
+ * 直读本 Socket SINT(读即清);不读全局寄存器——读即清且
+ * 唯一读者 = net_poll(F-NET-7)。
*/
- uint16_t gs = ch395f_get_glob_int_status_all();
- if ((gs & (1U << (sock + 4))) && /* socket 中断位(socket 0 → bit 4) */
- (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE)) {
+ if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) {
can_write = 1; /* 缓冲已腾空,下一轮写入下一块 */
} else if (HAL_GetTick() - tx_sw >= PHASE5_TX_TIMEOUT_MS) {
/* 超时:芯片迟迟未腾出缓冲 —— 对端未收/链路异常/芯片堵死 */
@@ -1655,12 +1580,12 @@ static void phase5_run(void) {
#endif
/* ===================================================================
- * Phase 15 — N34 阶段 2 驱动原语(无需 PC)
+ * Phase 15 — 自愈收敛阶段 2 驱动原语(无需 PC)
*
- * 测试目的:验证 ch395f 新增原语(实施方案 docs/网络/N34-自愈审计.md §5.4)
+ * 测试目的:验证 ch395f 新增原语(设计 docs/网络/网络自愈架构设计.md §4.4)
* 语义正确:类型化状态回读一致性 / cmd_done(E3 定案=状态回读)/
* sock_reset 非阻塞状态机跑通(E4 定案序列)/ 链路态缓存 / 事件护栏。
- * 污染会话下的复位载荷完整性已由 E4 bench 60 轮实证(实验方案 E4),
+ * 污染会话下的复位载荷完整性已由 E4 bench 60 轮实证(《网络自愈架构设计》§5.2 E4),
* 本 Phase 只验"状态机跑通 + 判据语义",不依赖 PC。
* 上下文:硬件层模式(testTask 独占 CH395F),用 Socket 7(产品监听 0~4 不涉)。
* =================================================================== */
@@ -1672,7 +1597,7 @@ static void phase15_run(void) {
int done;
int rc;
- DBG_INFO("=== CH395F Phase 15 Tests (N34 phase-2 primitives) ===");
+ DBG_INFO("=== CH395F Phase 15 Tests (self-heal phase-2 primitives) ===");
/* ---- TC-1501: 状态回读一致性 + cmd_done 语义(E2/E3 定案形态) ---- */
ch395f_set_proto_type(sn, CH395F_PROTO_TYPE_TCP);
@@ -1809,7 +1734,7 @@ void ch395f_test_run(void)
net_latency_phase12_run();
#endif
#ifdef ENABLE_PHASE15_TESTS
- /* Phase 15(N34 阶段 2 驱动原语):独占态下直调驱动,放 HW 层末尾 */
+ /* Phase 15(自愈收敛阶段 2 驱动原语):独占态下直调驱动,放 HW 层末尾 */
phase15_run();
#endif
}
diff --git a/test/board/net_latency_test.c b/test/board/net_latency_test.c
index 7d21cdf0..fea00399 100644
--- a/test/board/net_latency_test.c
+++ b/test/board/net_latency_test.c
@@ -447,6 +447,9 @@ void net_latency_phase12_run(void)
(void)ch395f_check_exist();
break;
default:
+ /* 例外(同 P1-06):被测对象就是本 API 的 SPI 时延——硬件层
+ * 独占上下文、无并发消费者;除此之外测试代码禁读全局寄存器
+ * (读即清,唯一读者 = net_poll,F-NET-7) */
(void)ch395f_get_glob_int_status_all();
break;
}
@@ -757,7 +760,7 @@ void net_latency_phase13_run(int32_t sockfd)
if (s_p13_state != P13_ST_ABORT) {
return; /* 正常完成/DONE:会话终态,不再处理 */
}
- /* N34 阶段1(F-NET-3):原"进 abort 分状态手动重同步 + 10s 周期 relisten"
+ /* 自愈收敛阶段1(F-NET-3):原"进 abort 分状态手动重同步 + 10s 周期 relisten"
* 已删——三种去向全部由 net 层自动机制认领(run8 静默回收滞留 ESTABLISHED
* → 对账⑤ RESYNC;CLOSED 失位 → pump 100ms 认领重建;LISTENING 静默丢
* 监听 → pump"芯片不在听"判据),手动 API net_resync_listen/net_relisten
diff --git a/test/board/net_test_task.c b/test/board/net_test_task.c
index 169c27b2..1b6c587f 100644
--- a/test/board/net_test_task.c
+++ b/test/board/net_test_task.c
@@ -803,7 +803,7 @@ static void phase10_run(int32_t sockfd) {
* 一直等待;防火墙规则对已建立连接无效,E11 实证)。
* 通过:从连接建立到 recv 返回 0 的间隔落在 10s~120s 有界窗
* (芯片定时语义与手册公式 75s 不符,实测判死 18~31s——
- * 见实验方案 E11 与 phase11_run 内判据注释)。
+ * 见《网络自愈架构设计》§5.2 E11 与 phase11_run 内判据注释)。
* =================================================================== */
#ifdef ENABLE_PHASE11_TESTS
diff --git a/test/host/README.md b/test/host/README.md
index f5b53cad..1e48ad83 100644
--- a/test/host/README.md
+++ b/test/host/README.md
@@ -25,8 +25,8 @@
| `ftp_abort_probe.py` | FTP 中止场景探针(TRAP-NET-18;RST→450 / FIN→226,`--check-abort`;2026-10-13 自 `MDK-ARM/` 迁入) |
| `ftp_integrity_test.py` | FTP 上传 SHA-256 完整性多轮回归 |
| `n30_repro.py` | 背靠背 STOR 复现器(TRAP-NET-20 常备回归腿) |
-| `relisten_stress.py` | 监听快速复连压力(TRAP-NET-17 / N34 E1:`--port --n --gap-ms [--hold-ms]`) |
-| `e11_keepalive_probe.py` | keepalive 黑洞判死探针(N34 E11) |
+| `relisten_stress.py` | 监听快速复连压力(TRAP-NET-17 / E1:`--port --n --gap-ms [--hold-ms]`) |
+| `e11_keepalive_probe.py` | keepalive 黑洞判死探针(E11) |
| `e6_soak.py` | E6 24h 挂机驱动(常态 + 每小时/每 4h E1 压测注入;判据读串口 `U15 probe` 计数) |
| `pasv_probe.py` | PASV 数据通道时序探针 |
| `ch395f_reuse_host.py` | Socket 复用/中止形态 PC 对端(bench 配套) |
@@ -115,7 +115,7 @@
- **PYTHONPATH/路径敏感**:脚本按仓库根相对路径读写,**从仓库根执行**
- 本目录**被 pre-commit 门禁的 cppcheck / 代码规范排除**(`test/` 豁免);
但各模块的 `run_unit_test.py` 自身**作为独立钩子**参与门禁(见上,共 7 个)
-- 台架探针族分工(2026-10-13 起):**N34/Trap 常备回归腿在本目录**(`relisten_stress` /
+- 台架探针族分工(2026-10-13 起):**自愈/Trap 常备回归腿在本目录**(`relisten_stress` /
`ftp_abort_probe` / `n30_repro` / `e02_probe` / `e6_soak` 等);`MDK-ARM/*.py` 只余
历史一次性探针(`ftp_freeze_probe` / `tcp_reconnect_probe` / `ftp_retr_abort_probe` /
`bench_verdict`)
diff --git a/test/host/ch395f_reuse_host.py b/test/host/ch395f_reuse_host.py
index b5c969dd..a030bcbd 100644
--- a/test/host/ch395f_reuse_host.py
+++ b/test/host/ch395f_reuse_host.py
@@ -1,7 +1,7 @@
# N-30 芯片级复用时序台架:主机端(与 test/board/ch395f_reuse_bench.c 共用变体表)
# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] [--mode legacy|e4]
# legacy: 按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。
-# e4: N34 E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接——
+# e4: E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接——
# ①污染:连上发 3000B 后 RST(SO_LINGER 0,TRAP-NET-18 中止形态);
# ②载荷:立即重连发 4096B 图案(pat[i]=(seed+i)&0xFF, seed=(rg*97+13)&0xFF)后 FIN。
# 端口 5100+rg,rg=0..(3*rounds-1),变体顺序 A/B/C(与固件 bench 表一致)。
diff --git a/test/host/e02_probe.py b/test/host/e02_probe.py
index 4b62a08e..81c0de5d 100644
--- a/test/host/e02_probe.py
+++ b/test/host/e02_probe.py
@@ -7,14 +7,14 @@
全体消费者的队列消息停摆最长 30s;
2. 2s 后从 PC 发起 FTP(:21) 连接,测 banner 到达时延 = 队列响应性:
修复前 ≈ 25~30s(队头冻结),修复后 ≈ 数百 ms 内;
- 3. 34s 时 30s 总预算应已耗尽:设备拆掉该会话(N34 阶段 3 net_drop_conn:
+ 3. 34s 时 30s 总预算应已耗尽:设备拆掉该会话(自愈收敛阶段 3 net_drop_conn:
FIN 关闭 + 本层收口 + 重监听),随后**本探针模拟守规矩对端**:
close 自己的 socket;
4. 芯片回 CLOSED(TC-NET-204 模式 A:TIME_WAIT 过渡 ~150ms)→ relisten,
:8888 应能再接受新连接(3s 重试窗内);新连接发完整 8 字节 TIME 包 →
设备应落 "wall clock updated"。
-⚠ 定案口径(TRAP-NET-07 定案更正 2026-10-11 + 实验方案 E13 §5):CH395F close
+⚠ 定案口径(TRAP-NET-07 定案更正 2026-10-11 + 《网络自愈架构设计》§5.2 E13):CH395F close
**无 RST 语义**。若对端内核存活(app 挂起但 socket 不关,内核自动 ACK FIN),
芯片停在 FIN_WAIT_2、监听恢复受限于对端 close——探针第 3 步的 close 正是
模拟"对端配合"这一可恢复边界;不配合场景的兜底是芯片 TIMEOUT/keepalive
diff --git a/test/host/e11_keepalive_probe.py b/test/host/e11_keepalive_probe.py
index 6f27e384..d8bb11d1 100644
--- a/test/host/e11_keepalive_probe.py
+++ b/test/host/e11_keepalive_probe.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""E11:CH395F 服务器侧 KeepAlive —— "链路在、对端静默"判死验证(PC 侧注入)。
-背景(N34 E11):手册 §5.56 规定 TCP 服务器应在 SINT_STAT_CONNECT 后开启
+背景(E11):手册 §5.56 规定 TCP 服务器应在 SINT_STAT_CONNECT 后开启
KeepAlive;本仓该调用 2026-10-12 被撤回,全仓仅剩客户端分支启用,而
App/ 无任何 net_connect() → 产品上 keepalive 实际关闭。本脚本用于验证
"开启后能否在有界时间内判死静默对端,且不误杀正常空闲会话"。
@@ -32,7 +32,7 @@ import time
TAG_C, TAG_D = 0xAA, 0xBB
CMD_KEEPLIVE = 0
-RULE_NAME = "N34-E11-DROP"
+RULE_NAME = "SELFHEAL-E11-DROP"
def _crc(data: bytes) -> int:
diff --git a/test/host/e6_soak.py b/test/host/e6_soak.py
index 79b42dbf..585147c1 100644
--- a/test/host/e6_soak.py
+++ b/test/host/e6_soak.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-"""E6 24h soak driver (U15 mismatch frequency, N34 experiment plan E6).
+"""E6 24h soak driver (U15 mismatch frequency, self-heal design anchor E6).
-Protocol (docs/网络/N34-自愈审计.md §6 E6):
+Protocol (docs/网络/网络自愈架构设计.md §4.8, anchor §5.2 E6):
24h normal operation + periodic E1 stress injection:
- every hour : relisten_stress :7003 n=10 gap=100 (UI-like churn)
- every 4th hour : full E1 (:7003 n=50 gap=0 + :21 n=50 gap=100)
diff --git a/test/host/ftp_integrity_test.py b/test/host/ftp_integrity_test.py
index 8e64ae56..246a3571 100644
--- a/test/host/ftp_integrity_test.py
+++ b/test/host/ftp_integrity_test.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""E12:CH395F SPI 缓存区 TSL 时序 A/B 的字节级校验工作负载。
-背景(N34 E12):手册 §7.4 规定读写收发缓存区命令需在"长度与数据之间"留
+背景(E12):手册 §7.4 规定读写收发缓存区命令需在"长度与数据之间"留
TSL(读 0.4us / 写 0.2us)。本仓 ch395f_write_send_buf / ch395f_read_recv_buf
把命令头与数据打包进**同一 DMA 突发**(零间隔)。本脚本用 SHA-256 逐轮校验
"上传→回读"是否字节一致,为 A/B 提供灵敏判据:
diff --git a/test/host/latency_peer.py b/test/host/latency_peer.py
index 21326609..dc255412 100644
--- a/test/host/latency_peer.py
+++ b/test/host/latency_peer.py
@@ -311,7 +311,7 @@ class RemoPeer:
def ftp_stress_worker(self, start_evt, stop_evt, hold_s):
"""Wait for the stress window to open, then hold an idle FTP login:
lftpd read_line -> blocking net_recv(0) inside netTask (old 30s default
- budget NET_RECV_TIMEOUT_MS, removed in N34 phase 1.5; today the wait is
+ budget NET_RECV_TIMEOUT_MS, removed in self-heal phase 1.5; today the wait is
an L3 pending recv -> this scenario must NOT freeze the queue anymore)
-> every queued message (incl. the LCDMEM response) waits behind it (E-02)."""
if not start_evt.wait(120.0):
diff --git a/test/host/pasv_probe.py b/test/host/pasv_probe.py
index c5d2359c..32d48d03 100644
--- a/test/host/pasv_probe.py
+++ b/test/host/pasv_probe.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
-pasv_probe.py — decisive probe for the "storm" signature (N34).
+pasv_probe.py — decisive probe for the "storm" signature (self-heal audit E8).
Per FTP round prints: device PASV port, PC data-socket SOURCE port, round
result, and timing. Hypothesis under test: the CH395F reuses the same
diff --git a/test/host/relisten_stress.py b/test/host/relisten_stress.py
index a92502ef..f6d4a26a 100644
--- a/test/host/relisten_stress.py
+++ b/test/host/relisten_stress.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-"""relisten_stress.py - listener reconnect stress (N34 E1/E5 bench leg).
+"""relisten_stress.py - listener reconnect stress (E1/E5 bench leg).
Connect -> optional hold -> immediate close, repeated at a fixed gap.
Counts refusals and connect latency distribution; non-ok rounds listed.
diff --git a/test/host/time_sync/time_sync_test.c b/test/host/time_sync/time_sync_test.c
index 7aac8da2..53347ea7 100644
--- a/test/host/time_sync/time_sync_test.c
+++ b/test/host/time_sync/time_sync_test.c
@@ -235,7 +235,7 @@ int net_close(int sockfd) {
return 0;
}
-/* ---- net_drop_conn(N34 阶段 3 E-02 收口):计数 + 记录 fd ---- */
+/* ---- net_drop_conn(自愈收敛阶段 3 E-02 收口):计数 + 记录 fd ---- */
static int s_drop_calls;
static int s_drop_socks[32];
@@ -511,7 +511,7 @@ static void tc_open_listener(void) {
}
/* ===================== TC-TS-020..029:handle_conn =====================
- * ⚠ N34 阶段1(2026-10-13)契约变更:handle_conn **不再** net_close
+ * ⚠ 自愈收敛阶段1(2026-10-13)契约变更:handle_conn **不再** net_close
* (单连接模式下 conn==listen,close 会连监听一起毁);会话收尾只返回外层,
* 由 time_sync_task 在 `conn != ls`(模拟器多连接后端)时才关数据 fd。
* 故本组断言一律为 s_close_calls == 0;任务级关闭见 TC-TS-040e。 */
@@ -530,7 +530,7 @@ static void tc_handle_conn(void) {
CHECK(s_set_unix_last == 0x12345678u, "TC-TS-020b epoch 按大端解析");
CHECK((s_recv_calls == 1) && (s_recv_len_log[0] == 16), "TC-TS-020c 首次请求 16 字节");
CHECK(s_recv_to_log[0] == 50U, "TC-TS-020d 单次等待片长 = 50ms");
- CHECK(s_close_calls == 0, "TC-TS-020e 处理完**不**关闭连接(N34 阶段1:close 交外层 conn!=ls 分支)");
+ CHECK(s_close_calls == 0, "TC-TS-020e 处理完**不**关闭连接(自愈收敛阶段1:close 交外层 conn!=ls 分支)");
CHECK(s_drop_calls == 0, "TC-TS-020g 成功会话不拆(drop 只用于放弃路径)");
CHECK(g_tstest_info_count == 1, "TC-TS-020f 留痕 wall clock updated");
@@ -605,7 +605,7 @@ static void tc_handle_conn(void) {
CHECK(s_recv_calls == 1, "TC-TS-027 预算耗尽后不再读(只读 1 次)");
CHECK(g_tstest_info_count == 1, "TC-TS-027b 留痕 no TIME packet in 30s, drop conn");
CHECK(s_close_calls == 0, "TC-TS-027c 预算耗尽也不关闭连接");
- CHECK(s_drop_calls == 1, "TC-TS-027d 预算耗尽主动拆会话(N34 阶段 3 E-02 收口:net_drop_conn 拆连接保监听)");
+ CHECK(s_drop_calls == 1, "TC-TS-027d 预算耗尽主动拆会话(自愈收敛阶段 3 E-02 收口:net_drop_conn 拆连接保监听)");
/* 9) 剩余预算不足一片 */
fakes_reset();
diff --git a/test/host/time_sync_server.py b/test/host/time_sync_server.py
index 9c8754fa..8b7d9efc 100644
--- a/test/host/time_sync_server.py
+++ b/test/host/time_sync_server.py
@@ -57,7 +57,7 @@ def main():
# 关键:发送后保持连接打开,给板子留出读完 8 字节的窗口。
# 若立即 close,CH395F 收到 FIN 会丢弃接收缓冲里尚未被板子读出的
# 8 字节时间包,导致板子 net_recv 看到 CLOSED 而无数据、时间同步失败。
- # N34 阶段1-5 起板子会话收尾**不再 close**(单连接监听跨会话常驻,
+ # 自愈收敛阶段1-5 起板子会话收尾**不再 close**(单连接监听跨会话常驻,
# EOF 时 net_close 会连监听一起毁掉)——这里 recv 等 2s 宽限后由
# 本端关闭;板子 net 层 auto_relisten 随即恢复监听,下一轮照常连接。
sock.settimeout(2.0)
diff --git a/tools/arch_diagram/gen_net_instability_svg.py b/tools/arch_diagram/gen_net_instability_svg.py
index 1be62011..34084a05 100644
--- a/tools/arch_diagram/gen_net_instability_svg.py
+++ b/tools/arch_diagram/gen_net_instability_svg.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-"""生成 docs/images/网络不稳根因总览.svg —— N34 复盘配图(纯标准库)。
+"""生成 docs/images/网络不稳根因总览.svg —— 自愈架构复盘配图(纯标准库)。
布局:左列 = 七个误会(问题环,玫红),右列 = 对应拆法(绿),行间水平箭头;
左列环间竖向细箭头 = "叠加累积";底部两个总结盒。
@@ -49,12 +49,12 @@ C_GRAY = "#94a3b8"
# ----------------------------- 数据(改这里) -----------------------------
# (问题标题, 问题副标, [问题芯片], 拆法标题, [拆法芯片])
RINGS = [
- ("① 门铃会丢", "TRAP-NET-17:丢中断",
+ ("① 门铃听不见", "TRAP-NET-17:事件未收到(E14 复核:非芯片丢,是被读清消费)",
["症状:UI 复连永久拒连、FTP 第 29 轮自己死掉,全程不报一个错",
- "机制:快速复连时 INT# 门铃哑火,netTask 只靠门铃干活 → 状态静默滞留"],
+ "机制:快速复连时门铃事件被读清消费,netTask 只靠门铃干活 → 状态静默滞留"],
"100ms 巡逻对账",
["单一 reconciler:判据读驱动状态缓存(E2 背书 18.1µs),分级去抖",
- "门铃丢了不要紧,每 100ms 亲自数一遍人头"]),
+ "门铃听不见不要紧,每 100ms 亲自数一遍人头"]),
("② 门铃总闸看一眼就清零", "TRAP-NET-20 B3",
["症状:别的 Socket 的 CONNECT/DISCONNECT 从此永久失踪",
"机制:全局中断寄存器整体读即清,open/relisten 丢弃式读一遍=拔全楼门铃线"],
@@ -101,11 +101,11 @@ RINGS = [
SUM_LEFT = ["七环叠加 =「玄学不稳」",
"随机 · 无错误日志 · 时好时坏——每个环节单看都「正常工作」"]
-SUM_RIGHT = ["逐环拆掉 = 稳(N34 阶段 0~3 已实施)",
+SUM_RIGHT = ["逐环拆掉 = 稳(自愈收敛阶段 0~3 已实施)",
"不依赖门铃 · 唯一主人 · 永不冻结 · 100ms 对账 · 开张前排渣——台架回归全绿"]
FOOT = ("证据链:docs/问题记录/CH395F_Trap_Records.md(TRAP-NET-07/-09/-12/-17/-18/-20) · "
- "docs/网络/N34-自愈审计.md §6 实验记录(E1~E13) · 修复落地见同文档 §5 实施方案")
+ "架构现状 docs/网络/网络自愈架构设计.md §2~§4 · 实证锚点同文档 §5.2(E1~E13)")
# ----------------------------- 工具函数 -----------------------------
@@ -192,10 +192,10 @@ def build():
add('' % (W, total_h))
add('%s'
- % (PAD, 40, TITLE_FONT, esc("为什么网络「一直不稳」——七个误会与逐个拆掉(N34 复盘)")))
+ % (PAD, 40, TITLE_FONT, esc("为什么网络「一直不稳」——七个误会与逐个拆掉(自愈架构复盘)")))
add('%s'
% (PAD, 62, SUB_FONT,
- esc("左列 = 根因(比喻 + 机制);右列 = N34 阶段 0~3 的对应拆法;环与环叠加才构成「玄学」")))
+ esc("左列 = 根因(比喻 + 机制);右列 = 自愈收敛阶段 0~3 的对应拆法;环与环叠加才构成「玄学」")))
for (pt, ps, lrows, ft, rrows, by, h) in placed:
draw_band(out, pt, ps, lrows, PAD, by, lw, h, C_PROB, C_PROB_BG)