Files
STM32F4-Base/docs/问题记录/CH395F_Trap_Records.md
T
wandering1 f849c79b7a feat(NET): SEND pending 二期落地 + 末块挂起越界缺陷修复(台架复测闭环)
net_pending.c/h 增设第二张挂起表 s_net_send[](与 recv 表完全独立,收发
互不阻塞);net_send_locked 快速路径改为有界自旋,单次等待超出
NET_SEND_SPIN_MS(2ms) 即登记挂起 send,由 net_send_pending_tick 按主循环
节拍分块推进,netTask 最坏停滞由 1s 压到 2ms。net_listener_retry_tick 的
挂起保险扩展到 send 侧;net_close_locked / 网络重建 / 状态对账三路均先
取消再动作(取消必通知,调用方永不挂死)。

台架复测(A/B 对照 + 插桩定位)发现并修复末块挂起越界缺陷:内层"等本块
发完(SENBUF_FREE)"位于写块之后,写完末块时 sent 已 == len,若该次等待越过
2ms 预算便会调用 net_send_pending_register,而登记函数按 sent >= len 拒收
并回 -1,把一次实际已完整交付芯片的发送报成失败。修复为内层等待开头判
sent >= len 即跳出(末块已交付、SENBUF_FREE 等待是纯保守自旋),并置于
超时/自旋判定之前;该守卫同时保证后续登记必伴随 sent < len。

复验:ftp_regression 连续 19 次 11/11;ftp_integrity_test 20 轮 x 64KB
STOR/RETR SHA-256 20/20 一致(两轮),吞吐稳定约 80KB/s;设备日志零
write error;ST-LINK 回读 No difference found。

文档:Net层阻塞模型架构设计.md 补二期设计与新增 9.1 缺陷记录,netTask
栈实测峰值按本次台架 920B 订正为 1116/3840(FTP 传输期 1164);网络自愈
架构设计.md 吸收原监听生命周期文档、新增编号术语导读表,并把 5.2
"实验锚点速查"的 E 编号含义与 E-02 审计编号歧义写在表头(用户反馈该表
E 编号无法读懂)。

门禁:Keil 0 错 0 警;cppcheck / 编码规范 / 乱码零告警;
check_mem_numbers PASS;check_doc_links --all --sections-strict 142 篇 PASS。
2026-10-14 12:05:21 +08:00

48 KiB
Raw Blame History

CH395F 驱动陷阱记录(TRAP-NET 系列)

CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修复、回归判据。改驱动/NET 层前先读本文。

编号口径:按 docs/编号规范.md 采用 TRAP-NET-<nn>,连续 0121。编号沿革(2026-10-13 全库重排): 旧 0111 不变;旧 14→12、15→13、16→14、17→12(并入)、18→15、20→16、22→17、23→18、24→19、 25→20、26→21;旧 12/13/19/21 已移出/并入/删除,无条目。旧形 Trap NN 为违规裸形已全库迁移; 旧号只存在于历史提交与旧日志。编号不复用、不重排。 状态图例:✅ 已修复定案 · ⏳ 待修。

索引

编号 标题 状态 一句话结论
TRAP-NET-01 Socket 配置顺序 ✅ TCP_LISTEN 时刻一次性扫描:先配数据 Socket 1~7,最后配监听 Socket 0
TRAP-NET-02 Socket 缓冲显式分配 ✅ 48 块×512B 几何,Socket 4~7 默认零块,必须显式分配且不重叠
TRAP-NET-03 DHCP xid 偏移 ✅ xid=data[4:8](Wireshark 旧版显示误导)
TRAP-NET-04 UDP 发送后等 SENBUF_FREE ✅ 不等则下次写入被静默丢弃
TRAP-NET-05 PHY 自动协商不兼容 ✅ 与 RTL8305NBI 直连协商失败,强制 100M 全双工
TRAP-NET-06 KeepAlive 参数 500ms 倍数 ✅ 非 500ms 倍数 = 定时器未定义行为;IDLE>INTVL
TRAP-NET-07 TCP 关闭重连 ✅ 直接 close,不要先 disconnect;close 无 RST(对端不配合则 FIN_WAIT_2 长尾,兜底靠查岗/net_drop_conn)
TRAP-NET-08 RECV 中断电平触发 ✅ 缓冲有数据就一直拉低,net_poll 每轮只处理一批
TRAP-NET-09 沉降窗(link up / close 后) ✅ open 失败重试 + net_sock_reset_sync 确定性排渣;LISTEN 态关闭是两段 CLOSE
TRAP-NET-10 消息队列值拷贝 ✅ 结果必须走 xTaskNotify 通知值回传
TRAP-NET-11 accept 拒绝 ESTABLISHED ✅ standalone_accept 后状态已 ESTABLISHED,入口检查须放行该组合
TRAP-NET-12 残留 DISCONNECT 污染下一连接 ✅ 滞后锁存 + SINT 粘滞 → 预清 + 反核实 + close 后排空
TRAP-NET-13 DHCP 后 IP 寄存器提交滞后 ✅ 状态位≠寄存器生效:连续 3 次一致才采信
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 快速复连事件面未收到 → 监听静默死亡 ✅ 周期对账 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 数据连接三连故障 ✅ 三重定案:状态回读补 accept + 幽灵甄别 / closed-with-data / 背靠背小段毒化 TX("丢中断"归因已被 E14 复核推翻)
TRAP-NET-21 IP 显示段序与对端地址 ✅ net_inet_ntoa 段序 MSB 首;对端地址用 net_getpeername,勿用 getsockname

第一章 初始化与配置

TRAP-NET-01 · Socket 配置顺序:先数据 Socket 后监听 Socket

  • 状态:✅(顺序规则适用于现行 net_init——net_socket.c 注释「TRAP-NET-01:先配数据 Socket 1~7,再配监听 Socket 0」)
  • 现象:Socket 4~7 的 CONNECT 中断从不触发,新客户端 SYN 被静默丢弃。
  • 根因(定案):CH395F 在 TCP_LISTEN 时刻一次性扫描当前所有可用 Socket 建立内部查找表;数据 Socket 若在 TCP_LISTEN 之后才配置缓冲区/协议/端口,自动分配逻辑不认识它们。
  • 修复:正确顺序——① 数据 Socket 1~7:SET_SEND_BUF → SET_RECV_BUF → SET_SOUR_PORT → SET_PROTO_TYPE_TCP(不调 OPEN_SOCKET,连接到达时芯片自动打开);② 监听 Socket 0:SET_SEND_BUF → SET_RECV_BUF → SET_PROTO_TYPE_TCP → SET_SOUR_PORT → OPEN_SOCKET → TCP_LISTEN。
  • 教训:docs/芯片手册/CH395F.md §9.2.6 要求数据 Socket 先设源端口再设协议类型(与监听 Socket 顺序相反)。DISCONNECT 后芯片自动关闭 Socket 但保留配置,可再分配。

TRAP-NET-02 · Socket 缓冲区必须显式分配且不重叠

  • 状态:✅(现行 net_init 固定布局 8×(4收+2发)=48 块即用满)
  • 现象:多 Socket 数据互相覆盖、收到错误数据或客户端挂死;未分配缓冲的 Socket 发送为固定垃圾内容(0x0028 填充)、接收存不进数据、CONNECT/RECV 中断不触发。
  • 根因(定案):芯片默认只为 Socket 03 分配独立收发缓冲(48 块×512B 用满,docs/芯片手册/CH395F.md §5.45),Socket 47 为零块;未显式分配或误用相同块号导致重叠。
  • 修复:每个 Socket 独占块(现行固定 6 块/Socket=4收+2发,与 MSS 解耦)。必须在 open_socket 之前调 ch395f_set_send_buf/set_recv_buf。
  • 教训:缓冲几何以 §5.45 的 48×512B 为准("32×1KB"说法有误,实测块 0~47 全部有效)。

TRAP-NET-05 · CH395F 与 RTL8305NBI 自动协商不兼容

  • 状态:✅
  • 现象:ch395f_get_phy_status() 恒返回 PHY_DISCONN,链路无法建立。
  • 根因(定案):CH395F PHY 与 RTL8305NBI-CG 直连时自动协商失败(板级组合)。
  • 修复:初始化协议栈后强制 ch395f_set_phy(CH395F_PHY_100M_FULL)。

TRAP-NET-06 · KeepAlive 参数必须为 500ms 倍数

  • 状态:✅(产品参数 60s/5s/3 的定案链见附录 A)
  • 现象:设置 KeepAlive 后 TCP 连接几秒内 TIMEOUT 断开。
  • 根因(定案):芯片内部定时器以 500ms 为基准,非 500ms 倍数 = 未定义行为;且 IDLE 必须 > INTVL。
  • 修复:idle=60000 / intvl=5000 / cnt=3(均 500 倍数)。

TRAP-NET-15 · FUN_PARA bit0 迷思:bit0 保留必须为 0

  • 状态:✅(net_init 用 ch395f_set_fun_para(0x08))
  • 现象:曾以为必须 0x09(bit0=1"多 Socket 模式")才能使用 Socket 1~7。
  • 根因(定案):bit0 在官方手册 V1C/V1E/V2.2/V2.3 的 §5.52 位表中一律「保留,必须为0」;官方 SDK 无 bit0 宏;官方例程传 0x02 却给 Socket 0~7 全配缓冲、官方 FTP 例程直接传 0x08。8 Socket 是芯片原生能力(_SN 索引寻址),真正的门槛是 §5.45 缓冲分配。概念讹传源:bit1「服务器多连接」指一个服务器能接几个客户端,不是能用几个 Socket。
  • 修复/验证:0x08 下 Phase 7 34/34 全通过,并发回显实测落在 sock2/4/5/6/7 → Socket 4~7 在 bit0=0 下完全可用。保留 bit0=1 的代价:违反保留位规定,后续固件版本一旦启用该位现网设备静默失效。
  • 台架前置:判"芯片行为"前先确认对端真的收到了 SYN——① PC 网卡落在 Public 防火墙配置文件 → 入站被静默丢弃,全量 CONNECT TIMEOUT 极易误判成芯片故障;② 残留 tcp_server 进程占端口不报错。

TRAP-NET-19 · SET_MAC 必须在 INIT_CH395 之前

  • 状态:✅(纯顺序调整,无 RAM 变化)
  • 现象:UI 配置的 MAC 与设备实际在线 MAC 不一致("滞后一次上电")。
  • 根因(定案):docs/芯片手册/CH395F.md §9.2.1 初始化顺序 = SET_MAC → SET_IP/GWIP/MASK → INIT_CH395;§5.7 SET_MAC_ADDR 写芯片内部 EEPROM,§5.13 INIT_CH395 从 EEPROM 加载 MAC——SET_MAC 放 INIT 之后则本次上电不生效。
  • 修复:net_init() 把 ch395f_set_mac_addr() 移到 ch395f_init() 之前;四个 SET 函数注释统一"必须在 ch395f_init() 之前调用"。

第二章 中断与事件一致性

TRAP-NET-08 · RECV 中断是电平触发

  • 状态:✅
  • 现象:net_poll() 中 RECV 中断无限触发,主循环永远得不到执行。
  • 根因(定案):接收缓冲区有数据期间 INT# 保持低电平,do{...}while(int_status!=0) 必然死循环。
  • 修复:net_poll() 每轮只处理一批中断,由主循环负责读数据。
  • 推论:RECV 是条件位(数据读空即落),所以 B3 的全量读清对 RECV 可下轮补、对其余位是永久销毁。

TRAP-NET-12 · 上一条连接的残留 DISCONNECT 污染下一条连接

  • 状态:✅(两级预清 + 排空为 TRAP-NET-18/25 排渣契约的前身)
  • 现象:close 后立即复用同一 Socket 重连,新连接被上一条连接的 DISCONNECT 事件秒断(生产:openOK 后紧接 DISCONNECTED, releasing;硬件层:新会话第一次读 SINT 即命中上一会话遗留事件)。
  • 根因(定案):① GET_SOCKET_STATUS 是纯查询不清中断;② DISCONNECT 可能在 CLOSED 达成同时或之后才锁存;③ SINT 粘滞到被读(close/open 都不清,B3 实证)→ 无人消费则跨会话存活。关闭过渡实测:自身 FIN 的 ACK + 本地 TIME_WAIT,0~150 ms。
  • 修复:
    1. 预清:新会话 OPEN 前读一次全局 + 本 Socket SINT 丢弃陈旧事件(net_connect_locked open 后清本 Socket 中断);
    2. 排空:net_close_locked 在 close_socket() 后轮询 GET_SOCKET_STATUS 至 SOCKET_CLOSED(上限 500ms,实测 LAN 几毫秒)再读空本 Socket 中断;
    3. 反核实:依据中断做结论前用 GET_SOCKET_STATUS 验证实际状态一致(负向事件与 ESTABLISHED 矛盾时以状态为准)。
  • 注意:B3 修复后预清只准读本 Socket SINT,不得再全量读 8 个(见第六章 B3)。

TRAP-NET-17 · 快速复连事件面未收到 → 监听静默死亡(E14 复核:非芯片丢事件)

  • 状态:✅(周期对账 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 + 轻负载。
  • 机制(设备侧日志实证):① 本层收不到对应事件;② 此后本层收不到任何中断,纯事件驱动无路可走;③ 故必须周期回读芯片状态对账。归因更正(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 分支:回读芯片状态——已不可监听即重建。
  • 踩坑记录(务必先读):基于单次状态读的重建动作放在 accept 热路径 → 把"刚 open/listen 尚未生效"的瞬态误判为失配 → 监听反复重建直至 relisten give up → 三个监听口全部死亡(风暴)。结论:凡基于单次状态读的重建动作,必须去抖。
  • 回归基线::7003 300ms×10:10/10;:21 100ms×50:50/50;:21 0ms/600ms×50:50/50;:7003 200ms×20:20/20;FTP 10×64KB SHA-256:10/10;relisten give up:0。三类失同步均有自愈实测计数留痕。
  • 对应文件:net_socket.c(net_reconcile_tick);MDK-ARM/tcp_reconnect_probe.py。

TRAP-NET-20 · FTP 数据连接三连故障——三重定案

  • 状态:✅(回归 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 但事件始终未到等待环——E14 复核:事件已被 drain 读清消费,非芯片丢失)。
  • 芯片级规律:CH395F 拆链收尾窗口变长且不可观测(状态显示 CLOSED 持续 1s+ 也不代表拆完,实测可 >2s 且时长不定)。窗口内重新 open+listen 三种形态:① 握手完成但 CONNECT 事件未收到(E14 复核:事件被台架 post-listen drain 或读清竞态吞掉,非芯片侧丢失)→ 吞数据;② 状态显示 0x04 幽灵态——旧连接拆链进行中,状态如实如此(E15 见下条;回读对端端口=旧连接端口);③ 旧连接滞后 DISCONNECT 打到新监听 → relisten 毁掉新连接 → 425。
  • 归因更正(E15,2026-10-14,官方出处):旧"芯片谎报 0x04"措辞推翻——芯片如实报告旧连接拆链进行中:① FUN_PARA bit3 SOCKET_CLOSE=1 为我方设置(net_init 版本门控 ≥0x46;§5.52②/§5.23:DISCONNECT/TIM_OUT 后芯片按设计不动 Socket 状态与缓冲,等 MCU 关闭——"保留现场"是自家配置的文档化行为);② 拆链走 TCP/IP 协议状态机(RFC 9293 §3.3.2 图 5;芯片状态码表见手册 §5.22,明言"其他状态芯片会自动处理并更新"):主动关闭 FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT(对端同时关经 CLOSING、同时关闭直达),TIME_WAIT 驻留 2MSL 才落 CLOSED——这就是拆链窗;TIME_WAIT 的协议目的即"防旧连接迟到报文影响新连接"(RFC 9293 §3.3.2),正是"断连即重连被拒"的协议层根源。重传默认 12 次(§5.17),实测重传耗尽 ≥3s(test/board/ch395f_test_task.c 超时分类实测)——与拆链窗实测 >2s 同量级;③ WCH 官方博客(《【WCH以太网接口系列芯片---CH395】CH395 TCP Server模式下客户端重连设置》,cnblogs sw111/18451802)明言"客户端主动断连并立即重新发起连接请求,此时 CH395 会拒绝该连接请求,因此需要程序中做一些特殊处理",官方方案 = 关闭完成后再 open+listen——即我们的 settle/quarantine 等待;幽灵甄别是"强制放行"路径的护栏(防把拆链中的连接当新连接 accept)。状态面从来是真——I3 就此加强。
  • 已证伪的修复(受控实验全部无效,列出防重踩):close 后固定沉降 100~3000ms / Socket 轮换(跨 socket 毒化)/ listen 后 close+重试 / 缓冲重分配 / close 前 TX 排空延时 / 禁用对账 tick(对账不是元凶)。
  • 定案一:双信源原则——中断只是快路径,状态寄存器才是真相:
    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 ② 契约。
  • 定案三:LIST 吞包 = 背靠背小段写毒化芯片 TX:决定性对照——同一目录,LIST #1(行间天然间隔)通过;LIST #4/#7(每行一次 net_send ~60B 背靠背小段)必挂。芯片对同一连接的背靠背多段小写入会毒化 TX(芯片报 SEND_OK 但对端全部丢弃,无 RST 无 DISCONNECT);RETR 1KB 满块连发不受影响。修复:send_list/send_nlst 整份目录一次性批量发送(static 1536B 缓冲;注意 lftpd_inet_write 成功返回字节数非 0)。
  • 与 TRAP-NET-17 的关系:周期对账对常驻监听器有效;FTP 数据通道是"活 1 秒就死"的短命监听器,对账②无幽灵甄别时既救不全又帮凶。本条把"看状态补 accept"内联进 accept 热路径(短去抖、无冷却)+ 端口甄别,对账②保留为兜底网。
  • 教训(总纲):CH395F 的"成功"(中断位/状态码)只代表芯片本地动作完成,不代表对端收到;判定必须回到对端可见证据(主机侧收发)+ 手册语义(FUN_PARA §5.52、状态码 §5.22)。
  • 对应文件:net_socket.c(net_lost_connect_probe/closed-with-data accept/net_reconcile_tick②/last_peer_port)、net_types.h、lftpd.c(批量发送);台架 test/board/ch395f_reuse_bench.{c,h} + test/host/ch395f_reuse_host.py(主分支存档)。

第三章 连接生命周期与自愈

TRAP-NET-07 · TCP 关闭重连:直接 close,不要 disconnect

  • 状态:✅ 定案(TC-NET-204 三模式各 5 轮 45 项判定)
  • 定案:
    模式 关闭序列 回 SOCKET_CLOSED 耗时 重开+重连+回显
    A close 0~150 ms(过渡态可见 TIME_WAIT 0x0A) 5/5 OK
    B disconnect → close 全部 +0 ms 5/5 OK
    C disconnect(不 close) 10s 内从未回到(byte0 恒 05H) 直接 open 必返 0x20 FAIL;补 close 后 +0ms 回 CLOSED
    1. close 无 RST 语义:§5.35 只说"关闭前自动断链";手册中芯片主动发 RST 仅 §5.29(监听占用)与 §9.2.6(多连接无可用数据 Socket)两处,均与 close/disconnect 无关;
    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 拆僵尸会话(判死窗 3090s,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 到达即自动走完——不搁浅。
  • 状态:✅(现行形态=open 重试环 + net_sock_reset_sync;启动错峰已由就绪屏障承担)
  • 现象:PHY_CHANGE 中断同一时刻 ch395f_open_socket() 返错(非 BUSY 超时),首次 listen 失败。
  • 根因(定案):PHY link up 后芯片内部协议栈需额外时间完成初始化;同一 net_poll() 迭代内立即 OPEN_SOCKET 可能返 ERR_BUSY 或其他错误码。与 FUN_PARA 无关。
  • 修复(现行形态):
    1. net_listen_locked/net_connect_locked open 失败重试 ×3(D10 net_listener_retry_tick 重试环);
    2. open 失败先走 net_sock_reset_sync(确定性排渣,内置二段 CLOSE,见下)再重试——最短等 100ms;
    3. 启动错峰走就绪屏障(SYS_EVT_NET_READY/FTP_READY),不用魔法延时。
  • 补充:LISTEN 态 Socket 的二段关闭(E2/E4 芯片实验,本条"沉降才能重开"的前置形态):
    • 对 LISTEN(byte0=05H/byte1=01H)发 CLOSE_SOCKET:命令返 SUCCESS 但只拆监听(byte1 01H→00H),byte0 恒 05H——按"等 byte0=00H"判据会永远等不到;第二次 CLOSE 才把 byte0 拉到 00H。即 LISTEN 态关闭是两段:拆监听 → 关 Socket。
    • 驱动侧已内置补发:ch395f_sock_reset_* 状态机 WAIT_CLOSE 段检测到"TCP 已 00H 而 byte0 仍 05H"自动补发第二次 CLOSE(契约见 Drivers/BSP/CH395F/ch395f.h)。E4 60 轮实证"复位后 open 必成"。
    • ⚠ 任何新写的"close 后等 CLOSED"逻辑必须走驱动复位原语,不要手拼"一次 CLOSE + 轮询 byte0"——会在 LISTEN 态上死等。

TRAP-NET-11 · net_accept_locked 拒绝 standalone_accept 的 ESTABLISHED

  • 状态:✅(现行 net_accept_locked 保留该放行逻辑)
  • 现象:FTP 客户端 TCP 连接成功(standalone accept),220 欢迎语不发送,lftpd_inet_accept() 死循环。
  • 根因(定案):入口检查 state != LISTENING 直接拒绝——standalone accept 后 state 已变 ESTABLISHED,永远到不了后续检查。
  • 修复:放行 standalone_accept && state == ESTABLISHED 组合。仅影响 standalone_accept 模式(PASV 数据通道、单连接 FTP)。

TRAP-NET-18 · 上传中止后首笔复连上传被静默截断(≤2048B)并误报成功

  • 状态:✅(三阶段全部闭环;回归判据常备:test/host/ftp_abort_probe.py)
  • 现象:客户端上传(STOR)中途断开(RST 或优雅 FIN 均可),紧随其后的第一笔复连上传被截断到 ≤2048 字节(= 芯片单 socket RX 缓冲大小),且服务端回报 226 成功 = 静默数据损坏;第二笔及以后恢复正常。
  • 证据(可信复现:生产固件、零固件探针):判据 = FTP 协议结果 + 紧接一笔正常上传的 SHA-256 往返 + 控制通道 NOOP 存活探测。RST 中止 10/10 复现;优雅 FIN 5/5 同样复现 → 凡"数据未发完即断开"均触发。对照(无中止)177 轮(8KB~1MB,含 100 轮零间隔浸泡)SHA-256 全匹配(MDK-ARM/ftp_freeze_probe.py)→ 中止路径是唯一触发条件。
  • 机制(设备侧日志实证):① 中止时数据 socket 释放(RST 时芯片报 sock4 TIMEOUT),net 层随即 relisten(监听端口复用);② 中止时在途数据(约一个缓冲=2048B 残渣)随后才落进芯片 RX 缓冲;③ 下一笔连接 accept 后 ~10-20ms 出现伪 DISCONNECTED, releasing (chip tcp=0x00),lftpd 读到 EOF → 把已到的 2048B 当整个文件 → 226;④ 再下一笔芯片已干净 → 正常(一次性)。缺陷位于 fd 复用生命周期(上一条连接的残留状态投递给下一条连接),不在 FTP 命令逻辑。
  • 边界甄别:gap 500/2000/5000ms 复测实存尺寸全部仍 2048(12/12)→ 与时间无关;触发=设备侧收过数据的数据连接被对端关闭(纯 FIN 即触发);同 fd 非 FTP 服务(:7003)不受影响(不按传输复用)。
  • 修复(三阶段,net_socket.c + lftpd.c):
    1. 异常释放(TIMEOUT/RST)隔离:handle_timeout_event 置 per-fd 隔离标记;net_relisten_locked 隔离期内持续排空芯片 RX(net_drain_chip_rx())+ 主动 close + 排空该 socket 中断 + 等 CLOSED/TCP_CLOSED,满 NET_RELISTEN_QUARANTINE_MS(400ms) 才 open+listen;上限 NET_RELISTEN_SETTLE_MS(800ms) 强制放行——必须小于 NET_RELISTEN_DEADLINE_MS(2000ms):取 2000ms 会耗尽重试预算 → give up → 监听器死亡;
    2. 正常释放(FIN)沉降:handle_disconnect_event 登记(仅 recv_bytes != 0 时——纯连接即关与只发不收免沉降保复连时序),relisten 前最短等 NET_RELISTEN_GRACEFUL_MS(100ms,实测悬崖:0/30ms 无效、100ms 稳定通过);日志区分 relisten settle/relisten quarantine;
    3. 应用侧硬化:net_sock_t.released_abnormal 标记(TIMEOUT 置 1/DISCONNECT 置 0,新 fd 与新监听清零——新连接不得继承上一条的释放原因);net_pending_tick 断连结束时按标记以 NET_ERR_CONNRESET 或 EOF(0) 回传;lftpd receive_file 错误路径 unlink 截断文件 + STOR 回 450。
  • 协议边界(不可判定,留痕):FTP STOR 无长度声明,"在途 FIN 关闭"与"正常完成"逐字节不可区分 → 优雅中止仍回 226;仅 RST/掉链(芯片 TIMEOUT)可识别 → 450。FIN 边界由客户端长度校验(SIZE/RETR 往返)兜底。
  • 回归基线:--check-abort RST 5/5(450)+ FIN 5/5(226 边界不变);followup 全过;正常往返 freeze SHA-256 全 MATCH;:7003 sink 复用 RST×4/FIN×4 全过;:8888 真实收包写 RTC 成功。
  • 与 TC-NET-604 的关系:本条是独立、已可信复现并已修复的真实缺陷;TC-NET-604 已结案为非缺陷(判读教训见第六章台架纪律)。现场"上传失败/文件不完整/像卡死"类报告优先用本条判据复核。

第四章 数据通路与数值语义

TRAP-NET-04 · UDP 发送后必须等待 SENDBUF_FREE

  • 状态:✅
  • 现象:UDP 发送后立即接收,接收长度恒 0 或数据错误。
  • 根因(定案):手册要求每次 WRITE_SEND_BUF 后必须等 SINT_STAT_SENBUF_FREE,否则下次写入被静默丢弃。
  • 修复:每次 ch395f_write_send_buf() 后轮询 ch395f_get_sock_int_status() 检查该标志。

TRAP-NET-14 · NET 层目的 IP 字节序写反 → CONNECT TIMEOUT

  • 状态:✅(字节序总口径,多处引用)
  • 现象:NET 层 net_connect() 返回 0 但 sock0 CONNECT TIMEOUT 永远连不上;同一芯片同一目标的硬件层 client 完全正常。
  • 根因(定案):net_connect_locked() 把 sin_addr.s_addr(网络字节序)按低字节在前拆成 remote_ip_arr[4] 交给 ch395f_set_des_ip()——但 SET_IP_ADDR_SN 族按点分顺序([192,168,1,2])写入才生效(硬件层 test_parse_ip 直出点分即成功;官方例程 CH395IPAddr={192,168,1,101} 同向)。芯片把 SYN 发往 2.1.168.192 → 网关丢弃 → TIMEOUT。⚠ 手册 §5.8 总括"所有 IP 输入输出命令字节顺序均为低字节在前"与官方例程自相矛盾,本仓以实测+例程为准。
  • 误导性排查:GET_REMOT_IPP 回读是小端——把写反的数组读回再按小端解释恰好又得 192.168.1.2,回读日志"看起来正确",掩盖真实写反。
  • 修复:① remote_ip_arr 改大端提取(arr[0]=(ip>>24)&0xFF …);② UDP net_send_locked() 同样修正;③ 诊断回读按小端语义重组再打印。
  • 字节序总口径(务必分清两套):
    • 写芯片 IP 四字节数组(set_des_ip/set_ip_addr/set_gwip_addr/set_mask_addr)= 点分顺序;
    • 16 位端口一律低字节在前(§5.25/§5.26),两者口径不同,勿把"大端数组"套到端口上;
    • 回读类命令(GET_REMOT_IPP/GET_IP_INF)若标注"低字节在前",打印前先按小端重组;
    • 本层 sin_addr.s_addr/remote_ip = 网络字节序数字;remote_ip_arr[4] = 点分顺序(net_types.h 注释)。

TRAP-NET-16 · 单次写超过发送缓冲区(写穿)

  • 状态:✅
  • 现象:单次发送 >1KB 时数据中段错乱、连接被对端 RST、或 WRITE_SEND_BUF 后芯片不再产生 SENBUF_FREE——发送卡死或后续字节丢失;只在 >1KB 时稳定复现。
  • 根因(定案):分块上限误取 NET_DMA_MAX_PAYLOAD(2KB)/软件缓冲(4KB),而每 Socket 的发送缓冲 = 本仓固定 2 块×512B = 1KB(§5.45 上电默认其实是 4 块=2KB,本仓布局固定 2 块、与 MSS 解耦);一次性写 2KB/4KB 直接写穿发送缓冲。
  • 修复:CH395F_SEND_BUF_SIZE_BYTES 宏 + NET_SEND_CHUNK_MAX = min(NET_DMA_MAX_PAYLOAD, CH395F_SEND_BUF_SIZE_BYTES)(现 1KB);net_send_locked/net_sendto 分块上限改用它。门控是 SENBUF_FREE(不是"等 SEND_OK");接收方向不受影响(只读 get_recv_len ≤ 接收缓冲 2KB)。
  • 注意:上限随 CH395F_SEND_BLOCKS 自动跟随;调用方可按任意长度调 net_send,分块由驱动保证。

TRAP-NET-21 · IP 显示段序与对端地址(getsockname ≠ 对端)

  • 状态:✅(自愈收敛阶段 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:<高位端口>)。
  • 教训:显示层字节序 bug 不影响协议本身(FTP 功能全程正常),但会系统性误导排障——日志里的地址也要当被测对象核对。

第五章 上层集成与主机侧工具

TRAP-NET-03 · DHCP 包 xid 偏移错误(主机工具)

  • 状态:✅
  • 现象:Python DHCP Server 收到 DISCOVER 后无法匹配 Offer/Ack 的 xid。
  • 根因(定案):误用 data[232:236](Wireshark 某旧版显示误导);标准偏移 xid=data[4:8]、msg_type=data[242]、client_mac=data[28:34]、magic cookie=data[236:240]。
  • 对应:test/host/ch395f_socket_test.py DHCP server 模式。

TRAP-NET-10 · 消息队列值拷贝导致结果无法回传

  • 状态:✅(现行契约:net_* 错误码走通知值——AGENTS 模块约定)
  • 现象:net_send/net_recv/net_listen/net_accept/net_close 等委托 netTask 的 API 永远返回 -1,即使内部成功。
  • 根因(定案):osMessageQueue 传 net_msg_t 是值拷贝——netTask 修改的是队列副本,调用方栈变量的 msg.result 从未被更新。
  • 修复:结果搭 xTaskNotify(caller, (uint32_t)msg.result, eSetValueWithOverwrite) 通知值回来,调用方 xTaskNotifyWait 取回。勿给 net_msg_t 加"回传用"字段(调用方只会读到栈垃圾)。
  • 注意:指针成员(msg.buf)不受值拷贝影响;内核级 _locked API 直接操作不受影响。

TRAP-NET-13 · DHCP 成功后 IP 寄存器提交滞后

  • 状态:✅(测试侧稳定轮询+交叉比对)
  • 现象:GET_DHCP_STATUS 已置 0x00 后立即 GET_IP_INF 读到的仍是旧静态配置,而芯片实际已按新租约收发——三方数据矛盾构成假阳性。
  • 根因(定案):DHCP 状态位仅代表协商完成;IP/MASK/GW 寄存器提交与状态置位异步,数百毫秒级窗口(实测 ~1s 内翻转)。
  • 修复:① 连续 3 次读 GET_IP_INF(间隔 300ms,上限 8s)一致且非零才采信;② PC 端把收到的宣告 IP 与租约比对兜底。
  • 对应:test/board/ch395f_test_task.c: phase4_run();docs/测试说明/CH395F_Test_Guide.md 阶段 4 风险表 #4。

第六章 审计遗留与待修(B 系列,冻结号)

2026-10-11 寄存器/数值口径全量审计(四路并行:驱动逐命令 + NET/测试逐调用点 + Trap 复核;三源交叉:docs/芯片手册/CH395F.md + WCH 官方例程 + git 历史)的未闭环项。A 系列与已修 B 项的结论已沉淀进上文条目,不再重复;B 编号冻结不重排。

# 严重度 位置 问题 状态
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 + 生命周期设计(自愈收敛阶段 3 已落):原理与决策记录 → docs/网络/网络自愈架构设计.md §1.1~§1.4、§4.6、§10

台架注入陷阱(方法论,持续补充)

  • Windows 出站防火墙规则不能模拟"对端静默死亡":WFP 对已建立连接有状态缓存,中途 New-NetFirewallRule -Direction Outbound -Action Block 不影响既有流——看似"KeepAlive 不生效",实则 ACK 一直正常往返。
  • 更糟的副作用:该规则会吃掉本机后来发出的 FIN → 设备侧留下幽灵 ESTABLISHED,之后所有新连接被 §5.29"向试图连接的远端发送 TCP RESET"直接 RST,表现为"端口拒连"——易误判成设备故障(实为自伤)。撤规则后设备自行恢复。
  • 能真正掐断链路的手段:Disable-NetAdapter(链路真 down)。⚠ 该 cmdlet 只有 -Name/-InterfaceDescription,没有 -InterfaceIndex;网卡名是中文,用 (Get-NetAdapter -InterfaceIndex N).Name 取值再传入;禁用/恢复必须写在 try/finally 里。
  • 仍未覆盖的场景:"链路仍在、对端静默"(NAT 会话过期、中间节点黑洞)没有安全注入手段(静态 ARP 黑洞被交换机泛洪兜底、删 IP 连带拆掉本机连接)——E11 用禁用网卡的近似注入完成定案。
  • 主机侧回显服务器收不到入站连接 → 全量 CONNECT TIMEOUT:本机对 C:\programdata\anaconda3\python.exe 有 Public 入站 Block 规则(C:\python314\python.exe 才是 Allow),而 .venv\Scripts\python.exe 的基座解释器正是 Anaconda python。Windows 防火墙块规则优先于放行规则 → 中途加 Allow 规则完全无效,只有 Set-NetFirewallProfile -Name Public -Enabled False 才通。症状极具误导性:芯片 ~1.6s CONNECT TIMEOUT、net_connect 仍返 0、[CONN] openOK 照常打印,看着像芯片故障,实为主机防火墙。固定跑法:try { 关 Public → 复跑 } finally { 开 Public }。
  • Phase 7 两条既有失败与改动无关,别追:① TC704 socket create #5 FAIL = E-23(NET_MAX_SOCKETS=8 减 3 常驻监听 → 并发上限 5,而 PHASE7_MULTI_COUNT 仍是 8);② TC703 要求 PC 服务器离线,与 701/702/704 环境互斥,同轮连跑必有一侧失真。
  • 对照组必须从干净态起测:上一腿若没死透就重启下一腿,对照组失真。每个样本前 stlink_flash.py reset + 等 12s 干净启动,并取多样本再比均值。
  • [PROF]/[PROF2] 字段不是计数器,别拿来判"任务卡死":netp = net_poll 段最长耗时(µs)、netn = 采样瞬间在段内时长(App/util/task_prof.c)→ 数值恒定只说明"没刷新最大值"。真正的停摆信号是 [ERR][WD] task stall;其 miss= 打印的是累计数(连续数封顶在容忍值 8),容忍 = 8 轮×100ms = 连续 800ms 有任务缺席即停喂、IWDG 2.5s 溢出(App/util/task_watchdog.c)。缺席位图解码:req=0x77 为 DEFAULT/NET/FTP/UI/TIMESYNC/ADC,mask=0x41 → 缺席 NET+FTP+UI+TIMESYNC = "netTask 卡住、所有做 net_* 的任务阻塞在队列+通知后面"的指纹。
  • 静默黑洞用临时探针抓状态,测完即删:B10 的孤儿态在日志里毫无痕迹,靠对账跳过分支加一条 TEMP orphan … 才直接看到;同一轮还顺手用探针否掉了自己的第一假设。假设写盘前先测,比推理便宜。
  • 判读通则(与 TRAP-NET-20 同源):看到 TIMEOUT 先算时序再归因——2s 与 60s 一眼可分;否则会把"重传超时"误记成"KeepAlive 生效",把相关性当因果。
  • 台架纪律(数据完整性,沉淀自已撤回的「recv_len 归零 / 接收冻结」家族):TC-NET-604 及其同族(含旧号 13 的"部分读后 recv_len 归零"机理——与 §5.34 明文相左)结案为非缺陷,可信判据(产品固件 + 真实 lftpd 上传 + RETR 回读 SHA-256)227 轮全过(含零间隔与 100 轮浸泡)。原"复现"的根源全在测试配方自身:固件内非回显读者吞数据(短收量恰等于被吞字节数 4736−4608=128=2×64)、分类脚本把通过报成 STALL。三条规则:① 完整性以 SHA-256 往返为真值,并扣除固件读者吞噬的字节;② 冻结/卡死类走真实生产路径 + 端到端校验(MDK-ARM/ftp_freeze_probe.py),卡住时用控制通道 NOOP 区分"数据面卡死"与"整层卡死";③ 禁止把"固件内非回显读者"与固定期望值混用在同一配方里。代码保留的 MSS 解耦(ch395f.h)与 OVERSIZE 金丝雀(ch395f.c)与撤回机理无关,勿回退。

主机侧测试工具过期

  • test/host/ftp_abort_probe.py 已修:原用匿名登录,N-19 后匿名被 530 拒 → TRAP-NET-18 两条腿假 0/5(round 1 PASV failed,且半认证会话占死 FTP 唯一会话,后续轮全变 ConnectionRefused)。改 admin/admin 后 RST 5/5、FIN 5/5。教训:跑既有回归腿前先确认凭据仍有效,否则会把工具过期读成固件回归。
  • 同族未修:test/host/ftp_up_dl_test.py、ftp_speed_test.py、ftp_test.py 仍用匿名登录 → 对当前固件必挂登录步;docs/测试说明/FTP_Test_Guide.md 推荐的这几个入口因此已失效(现行可用入口是 test/host/ftp_regression.py,默认 admin/admin)。

文档侧待修清单

  • 已撤回机理仍被当作现行约束(2026-10-13 已处置:CH395F_Test_Guide.md ×7、BSD_Socket_API_函数调用关系.md ×2、net_socket.c ×4、ch395f.h ×1 全部改写为"未证实、保守按一次读空执行")。残余:补一条"部分读 → 再查 recv_len"的定向 TC。
  • 陈旧数字:CH395F_Test_Guide.md:14/24/645/660/1141/1200(DMA 4100/载荷 4096,现 2052/2048)、:72/1201(NET_DMA_MAX_PAYLOAD=4096,现 2048;实际分块上限是 NET_SEND_CHUNK_MAX=1024)、:1020-1040(TC-NET-1002/1003 期望 4096/4097 单次 DMA)、docs/网络/BSD_Socket_API_使用指南.md:1246(NET_SEND_BUF_SIZE 宏已不存在)。
  • 悬空引用:Test_Guide.md:353/418 的"见风险 #4/风险表 #1"指向已删除的表(旧 19 悬空引用已于 2026-10-13 重排时从 BSD_Socket_API_函数调用关系.md 清除)。
  • 口径冲突:Test_Guide.md:1135 的 TRAP-NET-07 行仍写"卡 FIN_WAIT_2/close_socket(RST)"(与 TRAP-NET-07 定案冲突);:45 的"TC704 40/40"与 :880 的"PHASE7_MULTI_COUNT ≤5"矛盾(HEAD 宏值仍是 8);Drivers/BSP/NET/README.md:35 与 net_socket.c 的"正常 FIN 路径零改动"与 TRAP-NET-18 二阶段矛盾。
  • 证据通则:*.log 在 .gitignore 内 → 本文所有台架计数(40/40、34/34、227 轮、4 轮 11/11、13/13…)脚本在库、日志未入库 = 不可复核;TRAP-NET-20 引用的实验分支 n30-chip-bench(tip eddbede)不含 bench 源文件与 uvprojx 注册,裸机芯片台架无法从任何已提交状态重建(主分支存档见 test/board/ch395f_reuse_bench.c)。

附录 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 规格偏离(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,过程全文见其 §12 归档)。
  • 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/网络/网络自愈架构设计.md §4.7 与 §5.2 E11/E13)。
  • Socket 缓冲:默认只给 Socket 03;Socket 47 必须 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 链)。
  • UDP 模式:DesIP=0xFFFFFFFF → UDP Server(接收数据前 8 字节为信息头 reserved[2] src_port[2] src_ip[4],§9.2.4 前 2 字节是数据长度);DesIP=具体 IP → UDP Client(无信息头)。发送后必须等 SENBUF_FREE(TRAP-NET-04)。