Files
STM32F4-Base/docs/问题记录/CH395F_Trap_Records.md
T
wandering1 b445cbb464 docs(问题记录): CH395F Trap_Records 重组审计——合并、删误判、主题分章
- 合并同根因:Trap 14+17(残留 DISCONNECT 污染下一连接)、Trap 13+21
  (recv_len 归零/接收冻结家族,非缺陷结案);FTP 改造清单表去重入附录 B
- 删当时误判叙述(各留一行误判存档):Trap 07 FIN_WAIT_2/RST 说、
  Trap 18 bit0 迷思、Trap 25「芯片杀连接」解读;B1 KeepAlive 三段翻转
  合并为定案链(E11 转正)
- 规范化:顶部索引表+状态图例;六主题章+附录 A 速查/附录 B 存档;
  条目统一「状态/现象/根因(定案)/修复/回归判据/教训」格式
- 状态刷新:B5 对端 IP 显示已修(代码随收尾批提交)、B10 标 net_drop_conn
  根治出口;Trap 17/20 措辞、Trap 12 根因(按 ff.c)、日期口径就地更正
- 编号永不重排(代码注释 123 处引用);跨文件断链 1 处改条目名引用
  (生命周期设计说明 §877-878 → Trap 25「定案二」)
- 体量 1018 行/102KB → 420 行/64KB;doc-links 两文件通过
2026-10-13 17:12:57 +08:00

62 KiB
Raw Blame History

CH395F 驱动陷阱记录

记录开发过程中遇到的所有坑、根因、解决方案,避免重复踩坑。

2026-10-13 重组审计:合并同根因条目(14+17、13+21)、删除已被推翻的误判叙述(只留一行误判存档)、 按主题分章并加索引;条目格式统一为 ## Trap NN · 短标题 + 固定小节(状态/现象/根因(定案)/修复/回归判据/教训, 视情况取舍)。编号永不重排——代码注释大量引用 Trap NN,合并条目标题保留双编号; Trap 19 从未存在(悬空引用登记在第六章待修清单)。 状态图例:✅ 已修复定案 · ⏳ 待修 · ↩️ 已撤回(非缺陷) · — 从未存在。

索引

编号 标题 状态 一句话结论
01 Socket 配置顺序 ✅ TCP_LISTEN 时刻一次性扫描:先配数据 Socket 1~7,最后配监听 Socket 0
02 Socket 缓冲显式分配 ✅ 48 块×512B 几何,Socket 4~7 默认零块,必须显式分配否则数据重叠/中断不触发
03 DHCP xid 偏移 ✅ xid=data[4:8](Wireshark 旧版显示误导)
04 UDP 发送后等 SENBUF_FREE ✅ 不等则下次写入被静默丢弃
05 PHY 自动协商不兼容 ✅ 与 RTL8305NBI 直连协商失败,强制 100M 全双工
06 KeepAlive 参数 500ms 倍数 ✅ 非 500ms 倍数 = 定时器未定义行为;IDLE>INTVL
07 TCP 关闭重连 ✅ 直接 close,不要先 disconnect;close 无 RST(E-02 边界:对端不配合则 FIN_WAIT_2 长尾,兜底靠查岗/net_drop_conn)
08 RECV 中断电平触发 ✅ 缓冲有数据就一直拉低,net_poll 每轮只处理一批
09 沉降窗(link up / close 后) ✅ open 失败重试 + net_sock_reset_sync 确定性排渣;LISTEN 态关闭是两段 CLOSE
10 消息队列值拷贝 ✅ 结果必须走 xTaskNotify 通知值回传
11 accept 拒绝 ESTABLISHED ✅ standalone_accept 后状态已 ESTABLISHED,入口检查须放行该组合
12 FatFS 路径与 8.3 短名 ✅ 真约束=FF_USE_LFN=0 只认 8.3;本仓用 "0:" 卷前缀(原 #2/#4 根因已按 ff.c 更正)
13/21 「recv_len 归零 / 接收冻结」家族 ↩️ 合并结案:非缺陷,全部为台架假象;机理与 §5.34 相左已撤回
14/17 残留 DISCONNECT 污染下一连接 ✅ 合并:滞后锁存 + SINT 粘滞 → 预清 + 反核实 + close 后排空
15 DHCP 后 IP 寄存器提交滞后 ✅ 状态位≠寄存器生效:连续 3 次一致才采信
16 IP/端口字节序 ✅ IP 四字节数组=点分顺序;16 位端口=低字节在前,两套口径
18 FUN_PARA bit0 迷思 ✅ bit0 保留必须为 0(0x08);当年真正起作用的是缓冲显式分配
19 — — 从未存在(Test_Guide.md:878 悬空引用待清)
20 单次写超发送缓冲(写穿) ✅ 上限=本仓 2 块发送缓冲 1KB(NET_SEND_CHUNK_MAX),门控是 SENBUF_FREE
22 快速复连丢中断 ✅ 芯片丢 CONNECT/DISCONNECT → 周期对账 net_reconcile_tick(reconciler 之源)
23 上传中止后首笔复连被截断 ✅ 残渣投递给下一条连接 → 两级沉降/隔离 + 异常释放回 450 删截断文件
24 SET_MAC 顺序 ✅ §9.2.1:SET_MAC 必须在 INIT_CH395 之前(INIT 从 EEPROM 加载)
25 FTP 数据连接三连故障 ✅ 四重定案:丢中断窗口探针+幽灵甄别 / closed-with-data / 背靠背小段毒化 TX / 全局寄存器读即清(B3)

第一章 初始化与配置

Trap 01 · Socket 配置顺序:先数据 Socket 后监听 Socket

  • 状态:✅ 已定案(多连接模式虽已随 319d00e3 删除,顺序规则仍适用于现行 net_init——net_socket.c 注释「Trap 01:先配数据 Socket 1~7,再配监听 Socket 0」)
  • 现象:多连接模式下最多接受 3 个客户端,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 但保留配置,可再分配。
  • 发现时间:Phase 8 压力测试(原记 2025-07,早于仓库首提交 b1d05091 2026-06-05,日期不可考)。

Trap 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 独占块(历史多连接布局 2发+2收/Socket;现行 net_init 固定 6 块/Socket=4收+2发,与 MSS 解耦,见 Trap 21 解耦注)。必须在 open_socket 之前调 ch395f_set_send_buf/set_recv_buf。
  • 教训:缓冲几何以 §5.45 的 48×512B 为准(此前文档"32×1KB"记录有误,Phase 7 TC704 40/40 实测确认块 0~47 全部有效)。
  • 发现时间:Phase 8 压力测试(日期同 Trap 01,不可考)。

Trap 05 · CH395F 与 RTL8305NBI 自动协商不兼容

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

Trap 06 · KeepAlive 参数必须为 500ms 倍数

  • 状态:✅ 已定案(产品参数 60s/5s/3 的转正过程见附录 A「KeepAlive 定案链」)
  • 现象:设置 KeepAlive 后 TCP 连接几秒内 TIMEOUT 断开。
  • 根因(定案):芯片内部定时器以 500ms 为基准,非 500ms 倍数 = 未定义行为;且 IDLE 必须 > INTVL。
  • 修复:idle=60000 / intvl=5000 / cnt=3(均 500 倍数)。
  • 发现时间:Phase 2 测试(原记 2025-07 不可考;KeepAlive 实验实际对应阶段 11)。

Trap 18 · FUN_PARA bit0 迷思:bit0 保留必须为 0

  • 状态:✅ 已定案(net_init 用 ch395f_set_fun_para(0x08))
  • 误判存档:本节曾断言"bit0=单/多 Socket 模式,必须 0x09 才能用 Socket 17"——无任何出处,已撤回;当年让 Socket 17 真正可用的是缓冲显式分配(Trap 02),不是 bit0。
  • 根因(定案):
    1. 官方手册 V1C/V1E/V2.2/V2.3 的 §5.52 位表中 bit0 一律「保留,必须为0」;官方 SDK CH395INC.H 无 bit0 宏;
    2. 官方例程直接反例:EXAM5_TCP_Server 传 0x02(bit0=0)却给 Socket 0~7 全配缓冲;官方 FTP 例程直接传 0x08;全 EVT 包无一处 0x09;
    3. 8 Socket 是芯片原生能力(_SN 索引寻址),真正的门槛是 §5.45 缓冲分配;
    4. 概念讹传源:bit1 FUN_PARA_FLAG_TCP_SERVER 的"服务器多连接"指一个服务器能接几个客户端,不是"能用几个 Socket"。
  • 为什么 0x09 当年"看起来有效":芯片忽略保留位;TC704 40/40 与 bit0 结论出自同一提交,属自证,无"0x08 下不通过"的对照。留着 bit0=1 的代价:违反保留位规定,后续固件版本一旦启用该位现网设备静默失效。
  • 实测闭环(2026-10-11,N-31 已销项):FUN_PARA=0x08 下 Phase 7 34/34 全通过(PHASE7_MULTI_COUNT 降到 5:E-23 未修,8 fd 减 3 常驻监听),并发回显实测落在 sock2/4/5/6/7 → Socket 4~7 在 bit0=0 下完全可用。
  • 台架前置(本轮踩到):① PC 网卡落在 Public 防火墙配置文件 → 入站 8081 被静默丢弃,TC701/702/704 全部 CONNECT TIMEOUT,极易误判成"0x08 毁了 client";② 上一轮遗留 tcp_server 进程占着 8081,新服务器绑不上端口却不报错。判"芯片行为"前先确认对端真的收到了 SYN(Trap 25 总纲铁律的又一次命中)。
  • 发现时间:阶段 7 扩展 8 Socket 测试,2026-08-25;定案更正 2026-10-11。

Trap 24 · 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 之后则本次上电不生效。原码顺序相反;ch395f.c 原注释把四个 SET 标为"芯片已初始化(之后)"加剧误排。
  • 修复:net_init() 把 ch395f_set_mac_addr() 移到 ch395f_init() 之前;四个 SET 函数注释统一改"必须在 ch395f_init() 之前调用";net_socket.h 同步。
  • 发现时间:2026-10-04(静态审查项 P1-7)。

第二章 中断与事件一致性

Trap 08 · RECV 中断是电平触发

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

Trap 14/17 · 上一条连接的残留 DISCONNECT 污染下一条连接(合并条目)

  • 状态:✅ 已定案(两级预清 + 排空为 Trap 23/25 排渣契约的前身)
  • 合并说明:Trap 14(2026-09,硬件层 TC-203 观察)与 Trap 17(2026-08-25,生产 net_close_locked)为同一根因的两层表现,2026-10-13 合并;代码注释常以「Trap 14/17」连用。
  • 现象:
    • 硬件层(14):close_socket → 已确认 CLOSED → 新会话第一次读 GET_INT_STATUS_SN 读到上一会话遗留的 DISCONNECT(0ms 即命中),负向判定/状态机误判(TC-203 三轮重连后 PC 握手实际成功、固件判"未连接")。
    • 生产层(17):单连接模式 close 后立即复用同一 Socket 重连,每次都 openOK 但紧接着 DISCONNECTED, releasing 秒断——旧连接的 DISCONNECT 在新连接 ESTABLISHED 之后才到达,handle_disconnect_event 把新连接误判断开。
  • 根因(定案):① GET_SOCKET_STATUS 是纯查询不清中断;② DISCONNECT 可能在 CLOSED 达成同时或之后才锁存;③ SINT 粘滞到被读(close/open 都不清,Trap 25 B3 实证)→ 无人消费则跨会话存活。
  • 修复:
    1. 预清:新会话 OPEN 前读一次全局 + 本 Socket SINT 丢弃陈旧事件(phase2_tc203() 双保险;生产 net_connect_locked open 后清本 Socket 中断);
    2. 排空:net_close_locked 在 close_socket() 后轮询 GET_SOCKET_STATUS 至 SOCKET_CLOSED(上限 500ms,实测 LAN 几毫秒)再读空本 Socket 中断;
    3. 反核实:依据中断做结论前用 GET_SOCKET_STATUS 验证实际状态一致(负向事件与 ESTABLISHED 矛盾时以状态为准)。
  • 措辞更正(2026-10-13):原 Trap 17 写"芯片要等对端 FIN/ACK 才置 CLOSED"——按 Trap 07 实测应为:自身 FIN 的 ACK + 本地 TIME_WAIT 过渡,0~150 ms。
  • 注意:B3 修复(2026-10-12)后预清只准读本 Socket SINT,不得再全量读 8 个(见第六章 B3)。
  • 发现时间:17=阶段 7 上板实测 2026-08-25;14=2026-09 TC-203 取证。

Trap 22 · 快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡

  • 状态:✅ 已修复定案(周期对账 net_reconcile_tick 是 N34 reconciler 的前身;阶段 3 已并入单一 100ms tick)
  • 现象(生产固件实测):单连接监听 socket 快速复连后永久失效(后续 SYN 被拒或超时,10s 内不自恢复),芯片其余部分完全正常(其他监听端口照常工作)。两种表现:
    • 拒连:芯片完成握手但 CONNECT 中断未上报 → 控制块滞留 LISTENING;或本层因 DISCONNECT 中断丢失仍以为有客户端 → 两种情况芯片侧都占住单连接监听,后续 SYN 全被拒;
    • 应用连锁:net_send 被拒;或应用读不到 EOF(g_client 永不释放,永不调用 accept)。
  • 复现(可信判据,纯客户端;工具 MDK-ARM/tcp_reconnect_probe.py):
    • 确定性:产品固件 + 待机 60s,对 :7003 以 300ms 快速复连 → 修复前首 2 次成功、之后永久拒连(2/10);
    • 概率性:FTP :21 100ms×50 → 修复前第 29 轮起永久拒连;对照 :21 0ms/600ms 与 :8888 同期正常 → 触发条件 = 复连间隔 <~1s + 轻负载。
  • 机制(设备侧日志实证):① 芯片丢 CONNECT 或 DISCONNECT 中断;② 此后本层收不到任何中断,纯事件驱动无路可走(把 LISTENING 分支改"重建监听"仍无效——连 DISCONNECT 都没来);③ 故必须周期回读芯片状态对账。裸机芯片台架(剥离 OS/net/FTP)独立复现"握手完成但 CONNECT 中断被丢"——芯片天性,不是固件 bug。
  • 修复(net_socket.c):
    1. 周期对账 net_reconcile_tick()(初版 200ms;N34 阶段 3 与 pump 合并为单一 100ms tick):修正失同步形态——①本层 ESTABLISHED 而芯片已无连接→按断开处理;②本层 LISTENING 而芯片已握手→补 accept;③本层 LISTENING 而芯片监听消失→重建;④孤儿监听(B10)→重建。去抖+冷却:同一失配连续 2 次才动手,动手后冷却 1s。
    2. handle_disconnect_event LISTENING 分支:由"仅告警"改为"回读芯片状态——已不可监听即重建"。
  • 踩坑记录(务必先读):最初修法放在 accept 路径(每次 accept 回读自愈):单次状态读把"刚 open/listen 尚未生效"的瞬态误判为失配 → 监听反复重建直至 relisten give up → 三个监听口全部死亡(风暴)。改"周期对账+去抖+冷却"后消除。结论:凡基于单次状态读的重建动作,必须去抖。
  • 验证(修复前→修复后)::7003 300ms×10:2/10→10/10;:21 100ms×50:28/50→50/50;:21 0ms/600ms×50:50/50/50/50;:7003 200ms×20:20/20;FTP 10×64KB SHA-256:10/10;relisten give up:0。自愈实测计数:chip closed -> disconnect 12 次、w/o CONNECT irq -> accept 1 次、listen lost -> relisten 1 次——三类失同步均真实出现并被自愈。
  • 对应文件:Drivers/BSP/NET/net_socket.c(net_reconcile_tick);MDK-ARM/tcp_reconnect_probe.py;docs/任务规划/README.md N-6(已销项)。
  • 发现时间:2026-09-22(台架偶发)→ 2026-09-23(确定性复现+当日修复闭环)。

Trap 25 · FTP 数据连接三连故障——四重定案(N-30)

  • 状态:✅ 已定案并修复(回归 4 轮 11/11;B3 见第六章)
  • 现象(生产 FTP 回归,多轮复现):同一会话内第 2 次及以后的数据连接(LIST/STOR/RETR)两类失败,概率随上一条数据连接的负载与间隔变化(NAND 目录越大越易触发),与提交历史无关(D20 基线同样复现,当年"11/11 通过"是慢速台架+干净目录的运气):
    • 吞数据(SWALLOW):新连接建立成功、服务端 net_send 返回成功,客户端 20s 收不到任何字节;
    • 425:新连接被接受后 ~10-20ms 出现 DISCONNECT(chip tcp=0x00),accept 失败 → 425 Can't open data connection。
  • 定案实验(裸机芯片台架,剥离 OS/net/FTP):实验分支 n30-chip-bench,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 22 机制的窗口期形态)→ 吞数据;② 状态直接假报 0x04 幽灵态(回读对端端口=旧连接端口);③ 旧连接滞后 DISCONNECT 打到新监听 → relisten 毁掉新连接 → 425。
  • 已证伪的修复(受控实验全部无效,列出防重踩):close 后固定沉降 100~3000ms(2s 也挂过)/ 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(生产 LIST#4"accept 成功但数据被吞"的直接成因就是对账②曾把幽灵补 accept);
    3. 对账②同步加幽灵甄别;last_peer_port 登记点:handle_disconnect_event / handle_timeout_event / net_relisten_try(清零 remote_port 前转登)/ net_close_locked(跨 memset 保留——幽灵属芯片 Socket 级,与 fd 复用无关)。
  • 定案二:所谓"kill 模式"是寄存器语义误读(2026-10-11,双向验证)——误判存档:曾解读"芯片在 accept 后 10-20ms 杀连接",有误。真相:
    1. FUN_PARA=0x08 置 bit3 SOCKET_CLOSE=1(§5.52 ②):产生 DISCONNECT/TIMEOUT 后芯片不关 Socket、不清缓冲,就是留给 MCU 读出残留数据后再关;
    2. 探针现场 kill-check recv_len=1027 re-read st=0x05/0x00——Socket 仍 OPEN、TCP 已 CLOSED、RX 缓冲 1027B = STOR 载荷一个不少全在:芯片按规格保留现场,不是杀连接;
    3. 那个 DISCONNECT 是客户端正常 FIN(Python ftplib storbinary 发完即关数据连接,标准 FTP 行为,局域网小文件 ~5ms 完成)。
    • 425 真根因(竞态+自毁缓冲):客户端 5ms 内发完并关闭;accept RPC 赶到时 MCU 态已 CLOSED;旧 net_accept_locked 把 CLOSED 一律当"无连接" → net_relisten_try(close+open 清空芯片 RX 缓冲)→ 1027B 被我方亲手销毁 → 425。芯片全程无辜(此模式)。
    • 修复(closed-with-data accept):CLOSED + auto_relisten + 芯片 RX 有数据(GET_RECV_LEN_SN>0)→ 接受这条"已关但可读"连接(日志 closed-with-data accept (drain pending)),应用快路径排空,排空后 EOF 自然触发 relisten——对齐 §5.52 ② 契约。STOR #2 从 4/4 失败 → 4/4 通过。
    • 连带修正:台架 mode0"accept 后 10-20ms DISCONNECT"大概率同为误读(主机收完数据自己关的);但台架丢 CONNECT 中断模式(主机 got=0 + 芯片 0x04 + 无中断,主机端独立证据)仍成立,双信源探针修复保留。
  • 定案三:LIST 吞包 = 背靠背小段写毒化芯片 TX(2026-10-11):决定性对照——同一目录同一载荷,LIST #1(FAT 冷、行间天然间隔)通过;LIST #4/#7(FAT 热、每行一次 net_send ~60B 背靠背小段)必挂。芯片对同一连接的背靠背多段小写入会毒化 TX 整条连接(芯片报 SEND_OK 但对端协议栈全部丢弃,无 RST 无 DISCONNECT);逐块协议(等 SENBUF_FREE 再写)本身合规(§5.45),但芯片对该模式的内部处理不可靠;RETR 1KB 满块连发不受影响。修复:send_list/send_nlst 整份目录一次性批量发送(static 1536B 缓冲,snprintf 累积 + 单次写;溢出截断判 -1→550;注意 lftpd_inet_write 成功返回字节数非 0)。修复后 ftp_regression 4 轮 11/11 + ftp_auth_bench.py 13/13。
  • 与 Trap 22 的关系:Trap 22 周期对账对常驻监听器有效;FTP 数据通道是"活 1 秒就死"的短命监听器,对账②无幽灵甄别时既救不全(冷却期内 accept 已超时)又帮凶(把幽灵补 accept)。Trap 25 把"看状态补 accept"内联进 accept 热路径(短去抖、无冷却)+ 端口甄别,对账②保留为兜底网。
  • 教训(Trap 25 总纲):本轮先后误读三次——把客户端正常 FIN 读成"芯片杀连接"、把 open 重试读成"拆链窗口"、把芯片假 SEND_OK 读成"数据已上线"。铁律: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(实验分支注册编译,主分支只存档)。
  • 发现时间:2026-10-11(登记簿 N-30)。

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

Trap 07 · TCP 关闭重连:直接 close,不要 disconnect

  • 状态:✅ 已定案(做法保留;原根因说法已推翻,见定案更正)
  • 误判存档:原断言"disconnect → close 让 Socket 卡 FIN_WAIT_2 数分钟、close 发 RST"——TC-NET-204 实测推翻(2026-10-11),机制说法作废,做法保留。
  • 定案(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(5/5);补 close 后 +0ms 回 CLOSED
    1. FIN_WAIT_2(0x06) 全程未出现(15 轮×9 采样点);
    2. close 无 RST 语义:§5.35 只说"关闭前自动断链";手册中芯片主动发 RST 仅 §5.29(监听占用)与 §9.2.6(多连接无可用数据 Socket)两处,均与 close/disconnect 无关;
    3. 观测口径错误:§5.22 第 1 字节只有 00H/05H,第 2 字节(TCP 态)仅在 byte0=OPEN 时有意义(官方 SDK 注释明文)——当年把"已 CLOSED 状态下的残留 TCP 态读数"读成了"卡在某个 TCP 态"。
    • 真正原因(C 模式 5/5 复现):FUN_PARA bit3 SOCKET_CLOSE=1(§5.52 ②)下产生 DISCONNECT 后芯片不关 Socket、不清缓冲,长期停在 05H/00H;此时直接 open_socket 必返 0x20 FAIL。把 Socket 拉回 CLOSED 的动作是 close,与 RST 无关。
    • 做法为何仍对:① §5.35 close 已自动断链,disconnect 纯属多余;② disconnect 会制造 DISCONNECT 事件,bit3=1 下必须被消费/预清,否则污染下一条连接(Trap 14/17/23 实证);③ 它把芯片推进 closing 过渡态,重开需沉降(Trap 09)。15 轮重开未观察到残留 DISCONNECT(0x10)——close 顺带清掉锁存。
    • 与 Trap 17 的张力已消解:实测支持 Trap 17 一侧(close 后 0~150ms 过渡),不支持"close 发 RST 瞬间回 CLOSED"。
  • 边界补充(2026-10-13,E-02 台架):close 无 RST 的适用条件——
    • 对端配合(收到 FIN 后其内核 close):芯片走 TIME_WAIT ~150ms → byte0 回 00H → relisten → 新连接接入;
    • 对端内核存活但不 close(挂起但连接仍活):本层发 FIN 后芯片停在 FIN_WAIT_2(byte0=05H/byte1=06H)长尾——没有 RST 就拆不掉,监听恢复受限于对端 close;
    • 不配合场景的兜底 = 芯片自身 TIMEOUT/KeepAlive 拆僵尸会话(E13 定案:判死窗 30~90s,见 docs/网络/N34-自愈审计/实验方案.md 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 到达即自动走完——不搁浅。
  • 判据:关闭后轮询 GET_SOCKET_STATUS 的第 1 字节等待 sock=0x00 再 open_socket;别拿 byte1 当"卡住"判据。
  • 发现时间:Phase 2 TCP Client 测试(原记 2025-07 有误,实为 2026-07-19;定案更正与 TC-NET-204 实测 2026-10-11;E-02 边界补充 2026-10-13)。
  • 状态:✅ 已定案(现行形态=open 重试环 + net_sock_reset_sync;原 osDelay(7000) 启动延时已被就绪屏障取代)
  • 现象:PHY_CHANGE 中断同一时刻 ch395f_open_socket() 返错(非 BUSY 超时),首次 listen 失败;3 秒后重试成功。
  • 根因(定案):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 ≪ 原 500ms 盲等;
    3. FTP 任务 osDelay(7000) 错峰 → 已由就绪屏障(SYS_EVT_NET_READY/FTP_READY)取代(2026-10 起)。
  • 补充(2026-10-13,E2/E4 台架):LISTEN 态 Socket 的二段关闭——本条"close 后需沉降才能重开"的前置形态:
    • 对 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 态上死等。
  • 发现时间:FTP 改造期(原记 2025-07 不可考);二段 CLOSE 补充 2026-10-13。

Trap 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)。
  • 发现时间:FTP 改造期(2026-08)。

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

  • 状态:✅ 已修复定案(三阶段全部闭环;回归判据常备:test/host/ftp_abort_probe.py)
  • 现象:客户端上传(STOR)中途断开(RST 或优雅 FIN 均可),紧随其后的第一笔复连上传被截断到 ≤2048 字节(= 芯片单 socket RX 缓冲大小),且服务端回报 226 成功 → 客户端据此认为上传成功 = 静默数据损坏;第二笔及以后恢复正常。
  • 证据(可信复现:生产固件、零固件探针;工具 test/host/ftp_abort_probe.py):判据 = FTP 协议结果 + 紧接一笔正常上传的 SHA-256 往返 + 控制通道 NOOP 存活探测。RST 中止 10/10 复现(请求 4096B → 设备日志 UPLOAD '/ab_ok.bin' 2048 bytes OK);优雅 FIN 5/5 同样复现 → 与 RST/FIN 无关,凡"数据未发完即断开"均触发。对照(无中止)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 命令逻辑——lftpd 只是把它放大成误报 226。
  • 边界甄别(现场"1~2s"时间假设被推翻):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 稳定通过,对 30ms 重试节拍留 3× 余量);日志区分 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(原误报 226)。
  • 协议边界(不可判定,留痕):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(t_stor 0.68→0.18s);:7003 sink 复用 RST×4/FIN×4 全过;:8888 真实收包写 RTC 成功。
  • 与 TC-NET-604 的关系:本条是独立、已可信复现并已修复的真实缺陷;TC-NET-604 已结案为非缺陷(Trap 13/21)。现场"上传失败/文件不完整/像卡死"类报告优先用本条判据复核。
  • 发现时间:2026-09-23(可信复现首轮);二阶段 09-24、硬化 09-25。

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

Trap 04 · UDP 发送后必须等待 SENDBUF_FREE

  • 状态:✅ 已定案
  • 现象:UDP 发送后立即接收,接收长度恒 0 或数据错误。
  • 根因(定案):手册要求每次 WRITE_SEND_BUF 后必须等 SINT_STAT_SENBUF_FREE,否则下次写入被静默丢弃。
  • 修复:每次 ch395f_write_send_buf() 后轮询 ch395f_get_sock_int_status() 检查该标志。
  • 发现时间:广播宣告模式(原记 Phase 6 2025-07 有误,UDP 对应阶段 3)。

Trap 13/21 · 「recv_len 归零 / 接收缓冲冻结」家族——非缺陷(合并结案)

  • 状态:↩️ 已撤回(2026-09-23 结案;2026-10-13 把 Trap 13 并入本条——同族误判,当年未同步)
  • 合并说明:Trap 13(2026-08-26,"分批读 recv_len 归零")与 Trap 21(TC-NET-604"恰好填满+排空窗口冻结")是同一轮误读的两半;Trap 13 的机理断言与手册直接相左,一并撤回。
  • 结案结论:原报告(同连接前序突发后 4096B 突发"前 2KB 正常回显、其余静默无回显")在当前固件上无法复现,判定为非缺陷。可信判据(产品固件 + 真实 lftpd FTP 上传,成功 = FTP 协议结果 + 上传后 RETR 回读 SHA-256)实测 227 轮全过(8KB/64KB/256KB/1MB;含零间隔快速重连与 100 轮浸泡),并发生产 UI 流 600 帧 CRC 全过。
  • 为什么原台架会"复现"(教训,务必先读):
    1. 测试程序自吞数据:台架配方里的固件内读者(Phase 9/11 的 64B 读、固件探针读)把数据读走后不回显——PC 端少收被误当"设备停摆"。实测短收量恰等于被吞字节数(4736−4608=128=2×64);对照组(无额外读者)40/40 全过。
    2. 判读工具误判:probe_classify.py 把通过的运行报成 STALL(脚本层实测 1460/1460 且内容逐字节匹配)。
    3. 由此派生的"部分排空致命 / 再武装区间 ≥160ms / 相位刀口"等结论全部作废。
  • Trap 13 历史注:当年确实观察到 recv_len 归零,但机理断言("读部分数据后 recv_len 被重置为 0")与 §5.34 明文相左("未读取的数据仍然保留在接收缓冲区中,可以继续读取"),本仓 TC-NET-201 注释也是相反口径;定案前须补一条"部分读 → 再查 recv_len"的定向 TC。⚠ 该已撤回机理仍被当作现行约束写在 CH395F_Test_Guide.md 多处与 net_socket.c 注释里——处置登记在第六章待修清单。
  • 台架纪律(保留):① 数据完整性以 SHA-256 往返为真值,并扣除固件读者吞噬的字节;② 冻结/卡死类走真实生产路径 + 端到端内容校验(MDK-ARM/ftp_freeze_probe.py),卡住时用控制通道 NOOP 区分"数据面卡死"与"整层卡死";③ 禁止把"固件内非回显读者"与固定期望值混用在同一配方里——本轮最大教训。
  • 保留的代码变更(与撤回机理无关):缓冲块布局与 MSS 解耦(ch395f.h)、超限读 OVERSIZE 金丝雀(ch395f.c)不回退(防御性/可观测性改进)。
  • 发现/结案:发现 2026-08-26(阶段 8);结案 2026-09-23;合并 2026-10-13。

Trap 16 · NET 层目的 IP 字节序写反 → CONNECT TIMEOUT

  • 状态:✅ 已修复定案(字节序总口径,多处引用)
  • 现象:NET 层 net_connect() 返回 0 但 sock0 CONNECT TIMEOUT 永远连不上;同一芯片同一目标的硬件层 client 完全正常。
  • 根因(定案):net_connect_locked() 把 sin_addr.s_addr(网络字节序)按低字节在前拆成 remote_ip_arr[4] 得到 [2,1,168,192] 交给 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 注释)。
  • 发现时间:阶段 7 NET 层 TCP Client 上板实测,2026-08-25。

Trap 20 · 单次写超过发送缓冲区(写穿)

  • 状态:✅ 已修复定案(措辞已按 2026-10-13 更正:不是"硬件 FIFO",是本仓分配的发送缓冲)
  • 现象:单次发送 >1KB 时数据中段错乱、连接被对端 RST、或 WRITE_SEND_BUF 后芯片不再产生 SENBUF_FREE——发送卡死或后续字节丢失;只在 >1KB 时稳定复现。
  • 根因(定案):分块上限误取 NET_DMA_MAX_PAYLOAD(2KB)/软件缓冲(4KB),而每 Socket 的发送缓冲 = 本仓固定 2 块×512B = 1KB(§5.45 上电默认其实是 4 块=2KB,本仓布局自 2026-09-22 起固定 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,分块由驱动保证。
  • 发现时间:阶段 8 大文件传输实现,2026-08-26。

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

Trap 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 模式。
  • 发现时间:阶段 4(原记 Phase 6 2025-07 有误)。

Trap 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 直接操作不受影响。⚠ 原注"net_connect() 不使用消息队列"已失效——现行 net_connect 同样走消息队列、结果同样走通知值。
  • 发现时间:FTP 改造期(2026-08)。

Trap 12 · FatFS 路径与 8.3 短名

  • 状态:✅ 已修复(修复有效;原根因 #2/#4 已按仓内 FatFs 源码更正)
  • 现象:FTP STOR f_open("/upload_test.txt", ...) 返 FR_INVALID_NAME(6);>8.3 的文件名同样 err=6。
  • 根因(定案,2026-10-13 更正):真约束是 ffconf.h FF_USE_LFN=0 → 只支持严格 8.3 短名(不得以点开头)。原记"FatFS 不接受 /file 前缀 / 空字符串失败"两条与仓内 Lib/FatFs/ff.c 矛盾(ff.c:3101-3109 会剥前导分隔符、:3127-3129 空名返回根目录)——已删;本仓现行路径口径是 "0:" 卷前缀(FF_FS_RPATH=0 下 "." 不是合法路径段)。
  • 修复:to_fatfs_path() 剥离 / 前缀(保留,行为正确);长文件名需 FF_USE_LFN=1 + FF_LFN_UNICODE(显著增 RAM,未启用)。
  • 发现时间:FTP 改造期(2026-08)。

Trap 15 · DHCP 成功后 IP 寄存器提交滞后

  • 状态:✅ 已定案(测试侧稳定轮询+交叉比对)
  • 现象:GET_DHCP_STATUS 已置 0x00 后立即 GET_IP_INF 读到的仍是旧静态配置,而芯片实际已按新租约收发——三方数据矛盾构成假阳性(2026-08-24 上板实测)。
  • 根因(定案):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。
  • 发现时间:2026-08-24。

第六章 2026-10-11 寄存器/数值口径全量审计(A/B 系列)

方法:四路并行——驱动层逐命令(含官方 SDK 逐字对照)、NET/测试层逐调用点、Trap 01–12 复核、Trap 13–25 复核。三源交叉:docs/芯片手册/CH395F.md + WCH 官方例程 CH395EVT/EXAM/PUB/CH395CMD.c、CH395INC.H + git 历史。

干净清单(核对过、无异常)

项 计数 结论
CH395F_CMD_* 命令码 55 与 §5 命令表 + SDK CMDxx_ 逐个一致,无一抄错
16/32 位量字节序 9 处 全部低字节在前,与手册明文 + SDK 逐字节一致
读命令输出字节数 14 条 无少读/多读(GET_SOCKET_STATUS=2、GET_IP_INF=20 且顺序 IP→GW→MASK→DNS1→DNS2、GET_UNREACH=8、GET_REMOT_IPP=6)
常量表 60 个 ERR_*18 / TCP_*11 / SOCKET_*2 / PHY_*6 / PROTO_*4 / SINT_STAT_*6 位号全对;唯一错值见 A3
GET_GLOB_INT_STATUS_ALL 拼装方向 — 与官方 CH395CMDGetGlobIntStatus() 逐字相同;glob & (1<<(sock+4)) = §5.16 bit4..bit11
get_socket_status 缓冲区 17 处调用点 全部 uint8_t st[2],无 1 字节残留
中断判据 17 掩码 / 32 全局读 / 27 SINT 读 位号与 §5.16/§5.23 一致;只用 16 位 ALL 版
单位与取值范围 12 个参数点 retrans 8/200ms、KeepAlive 60000/5000、ARP 100ms 单位、MSS 1024 全部合规
缓冲布局 3 处组合 net_init 8×(4收+2发)=48 块恰好用满、无交叠(零裕量)
显示假象 10 处 %d.%d.%d.%d 只有已定案的 net_socket.c getsockname 一处是 MSB 重排假象(见 B5),无新增同类

新发现(去重后按严重度)

# 严重度 位置 问题 状态
A1 中 test/board/ch395f_test_task.c:1005 TC-203 的 Trap 14 反核实写成 st_chk[0]==CH395F_TCP_ESTABLISHED——byte0 只有 00H/05H,条件恒假 → 防线永久失效且日志无痕(与 Trap 07 同类:byte0/byte1 语义越界) ✅ 已修(改 byte0==OPEN && byte1==ESTABLISHED)
A2 中 同上 :1150/:1321 Phase 3/4 把 GET_RECV_LEN 返回值直接当读取长度(本地 rx_buf[300]/[64],芯片 RX 2KB)→ 栈溢出 ✅ 已修(夹到 sizeof + 留痕)
A3 低 ch395f.h:253 CH395F_GINT_STAT_DHCPV6=0x8000(bit15);SDK 是 (1<<12)、§5.16 明确 [12:15] 保留 ✅ 已修(改 0x1000 + 注明本芯片恒 0;全仓零引用)
A4 低 ch395f.h/ch395f.c 多处 分组注释字节数错 4 处;KeepAlive 参数注释写"秒"(§5.53/5.54 是 ms);set_retrans_count 写"0=默认值5"(§5.17 默认 12)。实现全部正确,注释误导——B3 的 1 字节缓冲栈越界正是这类注释发起的 ✅ 已修
A5 低 net_socket.c 7 处注释 注释与手册/HEAD 相反:FUN_PARA(SOCKET_CLOSE=0)(实为 bit3=1,正是 closed-with-data accept 的契约依据)、"初始化成功后设置 MAC"(§9.2.1 ① 是之前)、"复用默认缓冲"、"布局由 MSS 推导"(Trap 13/21 已解耦)、"等 SEND_OK"(门控是 SENBUF_FREE)、UDP 头 [0-1] reserved(§9.2.4 是数据长度) ✅ 已修
A6 低 ch395f.c:1176 write_send_buf 超限静默 return(void、无日志),且上限取 DMA 2048 而非发送缓冲 1024;读路径有 OVERSIZE 金丝雀、写路径没有 ✅ 已修(补 [ERR] 留痕 + >1KB 告警,行为不变)
B1 中→定案 net_socket.c(KeepAlive 使能点) 原判"产品路径从未启用 Socket KeepAlive → 静默断开无人知晓"(§5.56 服务器侧应在 CONNECT 后开)。2026-10-12 曾推翻前提:禁用 PC 网卡(链路层死亡)芯片 +2s 即报 sock0 TIMEOUT(=§5.17 重传 8×200ms 量级,与 KeepAlive 无关)→ 依 Trap 25 铁律撤回使能。2026-10-13 E11 再定案:用"禁用网卡"注入"链路在、对端静默"(防火墙规则对已建立连接无效,方法学见 docs/网络/N34-自愈审计/实验方案.md E11)实测服务器侧开启后 60s/5s/3 → 判死 ≈30.0s(早于手册公式 75s,以实测为准);短参数 6s/2s/2 → ≈5.1s(参数生效);静默但存活对端不被误杀(90s 空闲回显正常) ✅ 已转正(handle_connect_event LISTENING 分支按 docs/芯片手册/CH395F.md §5.56 开启;Phase 11 回归 TC-NET-1101 已更新死对端注入法)
B2 中低 net_socket.c(net_connect_locked) ch395f_tcp_connect() 返回值被丢弃。§9.2.2 ⑧:GET_CMD_STATUS SUCCESS 仅表示命令执行成功,丢弃它 = 命令级被拒要等 30s recv 超时才暴露 ✅ 已修(2026-10-12):非 SUCCESS 且非 BUSY 才判失败(poll_cmd_status 超时返 ERR_BUSY=芯片仍在执行,当失败会误杀慢芯片);明确失败 → [ERR] rejected by chip: 0xNN + 按 §9.2.2 ⑧ 关 Socket 回滚 + 返 -1。TC701/702 回归无误杀。⚠ 残余: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 又粘滞到被读(Trap 14/17 实证)→ 全量读走即永久销毁其 CONNECT/DISCONNECT/TIM_OUT/SENBUF_FREE(唯 RECV 是条件位可下轮补,Trap 08)。git 考古:多连接时代引入 → 单连接搬迁 → relisten 复制,不属于任何已登记 Trap 的修复;官方 SDK 21 个例程一律"全局位图门控 + 单 Socket 读 + 立刻转存",无一例全量读 8 个 SINT ✅ 已修(2026-10-12):两处收窄为只清本 Socket SINT + 删除丢弃式全局读清(全局交 net_poll 集中读清=AGENTS 模块约定)。回归:Trap 25 11/11×3、Trap 23 RST 5/5+FIN 5/5、Trap 22 全腿与基线一致。⚠ 残余:net_connect_locked/net_close_locked/net_relisten_locked 三处指定预清仍含无条件全局读清(会搁浅而非销毁其它 Socket 挂起事件)——它们是 Trap 14/17/23 的两级预清,宜与 B4 一并处置
B4 低 net_socket.c:446/469 全局中断 bit0-3(UNREACH/IP_CONFLI/PHY_CHANGE/DHCP)被读清但从不消费;ch395f_get_unreach_info() 全仓零调用 → IP 冲突、ARP 不可达、链路抖动三类事件完全静默 ⏳ 待修(可观测性缺口)
B5 低 net_getsockname → lftpd.c 日志 net_getsockname 的 s_addr 组装方向与本层约定相反 + net_inet_ntoa 段序反转(NET_NTOHL 后主机数字的 MSB 才是首段,原实现低段在前)→ FTP 日志把 IP 打成 100.1.168.192;且 lftpd 打印的是 getsockname=本机 IP:21 而非对端。⚠ PASV 227 消费点做了 net_htonl 再 MSB 拆,双重反转恰好正确,勿误判 ✅ 已修(2026-10-13,N34 阶段 3 收尾;代码随本分支收尾批提交):net_inet_ntoa 段序改 MSB 首(MCU+模拟器同步);新增 net_getpeername(读 CONNECT 事件缓存,零 SPI);lftpd 改打印对端 IP:port(台架判据:日志 connection received from 192.168.1.x:<高位端口>)。PASV 未动(原本正确)
B6 低 net_socket.c:591/1225/3608 3 处状态判据只看 byte1、未按 §5.22 用 byte0 门控;reconcile 的 else-if 链里 (00H, 陈旧 04H) 会先命中"补 accept"而非"重建监听" ⏳ 待修(Trap 07 实测 byte0=00H 时 byte1 读回 0x00,故为低概率)
B7 低 ch395f_test_task.c:1379/1504 Phase 5 单次写 4093B 超 1KB 发送缓冲与 2048 DMA 载荷 → 整块被丢;注释仍写"4096B=发送缓冲上限"(旧事实)。A6 修后会打 [ERR] ⏳ 待修(chunk 改 ≤1024 或分块)
B8 低 ch395f_test_task.c:1290-1292 Phase 4 把 sock6 缓冲重排到块 24-27,与 net_init 固定布局中 sock4 的 24-27 交叠(§5.45 禁止) ⏳ 待修
B9 低 ch395f_reuse_bench.c:93、net_socket.c:39/262 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,四方互等。实测探针 TEMP orphan sock2 state=0 port=7003 chip=0x05/0x00 持续 12s+,:7003 300ms×10 0/10。与 Trap 22 的关系:①③两类都要求本层处于 ESTABLISHED/LISTENING,孤儿态恰好落在两类之外 ✅ 已修(2026-10-12):对账新增形态④认领孤儿(in_use+state==CLOSED+auto_relisten+type==STREAM+local_port!=0,芯片侧不在 LISTEN)→ net_relisten_try。⚠ auto_relisten 是决定性门:少了它出向连接(Phase 7 TC701703 绑本地口)会被误判孤儿 relisten 成监听器。回归::7003 0/10→10/10;其余全腿与基线一致。⚠ ④ 的副作用:net_recv 的 EOF 是边沿——④抢先 relisten 会把慢消费者的 EOF 永久吞掉(g_client 不清、新客户端数据被旧会话静默吸收)。根治出口 = net_drop_conn + 生命周期设计(N34 阶段 3 已落,6394853):原理与分阶段方案 → docs/子系统设计/网络Socket监听生命周期设计说明.md(含:芯片为何永不自动回 LISTEN、fun_para=0x08 使 0x05/0x00 成为合法常驻态、RFC 1122 §4.2.2.18 与 POSIX accept 的规范冲突、12 个重建所有者与 U1U15 无人负责组合、芯片原生多连接模式(A′)为何对本产品不可用)

台架注入陷阱(方法论,2026-10-12 起持续补充)

  • Windows 出站防火墙规则不能模拟"对端静默死亡":WFP 对已建立连接有状态缓存,中途 New-NetFirewallRule -Direction Outbound -Action Block 不影响既有流——实测静置+阻断 250s 芯片毫无反应,看似"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 用禁用网卡的近似注入完成定案。
  • 主机侧回显服务器收不到入站连接 → Phase 7 全量 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 25 同源):看到 TIMEOUT 先算时序再归因——2s 与 60s 一眼可分;否则会把"重传超时"误记成"KeepAlive 生效",把相关性当因果。

主机侧测试工具过期(2026-10-12 实测)

  • test/host/ftp_abort_probe.py 已修:raw_login/followup_ok 原用匿名登录,N-19 后匿名被 530 拒 → Trap 23 两条腿假 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)。

文档侧待修清单(其余登记在此)

  • 最高优先级:Trap 13/21 已撤回的"部分读后 recv_len 归零"机理仍被当作现行约束写在 CH395F_Test_Guide.md:28/69/930/932/958、docs/网络/BSD_Socket_API_函数调用关系.md:196/362、net_socket.c 两处注释里——须同批处置,并补一条"部分读 → 再查 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 宏已不存在)。
  • 悬空引用:Trap 19 从未存在(Test_Guide.md:878 仍引用);Test_Guide.md:353/418 的"见风险 #4/风险表 #1"指向已删除的表。
  • 口径冲突:Test_Guide.md:1135 的 TRAP-07 行仍写"卡 FIN_WAIT_2/close_socket(RST)"(与 Trap 07 定案冲突);:45 的"TC704 40/40"与 :880 的"PHASE7_MULTI_COUNT ≤5"矛盾(HEAD 宏值仍是 8);Drivers/BSP/NET/README.md:35 与 net_socket.c 的"正常 FIN 路径零改动"与 Trap 23 二阶段矛盾。
  • 机理措辞(Trap 17/20 措辞、Trap 25 的 NET_MSG_RESYNC_LISTEN/GET_REMOT_IPP/§5.32+§5.23① 引用——2026-10-13 重组已在本文件内更正;Test_Guide.md 侧仍待同步)。
  • 已失效条目未标注(Trap 01/02/11 对现行初始化仍有效、Trap 09 osDelay(7000) 已被就绪屏障取代、Trap 10 注记失效、Trap 12 根因按 ff.c 更正——2026-10-13 重组已全部就地标注)。
  • 日期(Trap 01/02/03/04/06/07 的"2025-07"早于仓库首提交 b1d05091 2026-06-05;Trap 03/04/06 的 Phase 编号错——2026-10-13 重组已标注为"不可考/已更正")。
  • 证据通则:*.log 在 .gitignore 内 → 本文所有台架计数(40/40、34/34、227 轮、4 轮 11/11、13/13…)脚本在库、日志未入库 = 不可复核;Trap 25 引用的实验分支 n30-chip-bench(tip eddbede)不含 bench 源文件与 uvprojx 注册,裸机芯片台架无法从任何已提交状态重建(主分支存档见 test/board/ch395f_reuse_bench.c)。

附录 A · 驱动关键点速查(细节以对应 Trap 为准)

  • 初始化顺序:SET_MAC → SET_IP/GWIP/MASK → INIT_CH395 → SET_PHY(Trap 24;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 已作废,见 Trap 13/21)。⚠ TSL 规格偏离(N34 E12,2026-10-13 登记):手册 §7.4 要求缓存区命令"长度与数据之间"留 TSL(读 0.4us/写 0.2us),本仓两个缓存区函数把命令头与数据打包进同一 DMA 突发(间隔=0)属规格偏离;实测未发现危害(460 轮/≈58MB 双向 SHA-256 仅 1 次未复现异常,"两段+TSL"变体与现状无差异)→ 依 Trap 25 铁律暂不改产品路径,偏离与证据登记于此、变体留作待用补丁(完整 A/B 见 docs/网络/N34-自愈审计/实验方案.md E12)。
  • TCP 参数(必须在 INIT_CH395 之前):重传 SET_RETRAN_COUNT(默认 12,最大 20)×SET_RETRAN_PERIOD(默认 500ms,最大 1000ms);KeepAlive 参数 500ms 倍数、IDLE>INTVL(Trap 06)。
  • KeepAlive 定案链:默认关闭 → 服务器侧应在 SINT_STAT_CONNECT 后开启(§5.56)→ 2026-10-12 曾因"链路层死亡已被重传 +2s 覆盖"撤回使能 → 2026-10-13 E11 转正("链路在、对端静默"场景实测判死 ≈30s,参数生效、存活对端不误杀):handle_connect_event LISTENING 分支开启 60s/5s/3;消费链路 TIM_OUT → handle_timeout_event QUARANTINE → relisten → 端口恢复(详见第六章 B1 与 docs/网络/N34-自愈审计/实验方案.md E11/E13)。
  • Socket 缓冲:默认只给 Socket 03;Socket 47 必须 open_socket 前显式分配(Trap 02);现行固定布局 6 块/Socket。
  • TCP 关闭重连:直接 ch395f_close_socket(),不要先 disconnect;轮询 GET_SOCKET_STATUS 第 1 字节等 00H 再 open;第 2 字节仅在 byte0=OPEN 时有意义(Trap 07)。
  • SOCK_TIMEOUT:SINT_STAT_SOCK_TIMEOUT 不应视为致命——记录日志后继续操作(消费侧 QUARANTINE 见 Trap 23/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 04)。

附录 B · FTP 支持改造一次性问题(历史存档,Trap 已单列者不重复)

# 问题 处置
4 CH395F 多连接模式不支持多监听 架构约束:fun_para=0x00 单连接(后定案 0x08,见 Trap 18)
5 PASV 端口 0(net_bind 无自动分配) net_socket.c: s_dynamic_port
10 s_file_open 单文件限制 lftpd_io.c 全局变量(已知限制)
15 NET_RECV_TIMEOUT_MS=5s 控制通道超时断开 net_config.h: 30000(后随阶段 1.5 recv 语义再改)
16 receive_file 0 bytes 当作成功 lftpd.c: total==0 则失败
17 MobaXterm 连数据端口不发数据 外部客户端行为(Python 抓包确认,超时恢复)