fix(NET)+docs: lftpd 对端地址修复、E6 裁决收口、陷阱编号迁移重排 TRAP-NET-01~21

- lftpd:net_inet_ntoa 段序改 MSB 首 + 新增 net_getpeername,FTP 日志打印
  真实对端地址(真机实证 192.168.1.2:55524,登记 TRAP-NET-21)
- E6:NET_U15_PROBE 命中=0(含压测)→ 阶段 3 项 4 不立项,U15 登记观察项;
  插桩留仓(宏=0 零成本),soak 驱动 test/host/e6_soak.py 入库
- 编号:全库 Trap NN → TRAP-NET-<nn> 迁移并重排连续 01~21(单调映射,
  沿革见 CH395F_Trap_Records.md 头注);死号 12/13/19/21 引用全部清除;
  GD5F 域引用补正为 TRAP-GD5F-NN
- 陷阱文档全新化:索引 21 行行行真陷阱;删误判存档/发现时间/git 考古/
  FTP 附录;台架纪律教训沉淀第六章方法论
- RAM:Code 211348→211456(+108 lftpd 修复),RW+ZI 净增 +4B;
  优化方案/台账受管区/tsv 同笔同步
- ftp_abort_probe.py 挪 test/host/;引用关系图重生成
- 回归:@build 0/0;@test PASS;ftp_abort 10/10;relisten :7003 20/20;
  ci_local L0+L0.5 全绿;E6 窗口零复位,7 条 WD stall 登记压测假象备查
This commit is contained in:
2026-10-13 20:54:31 +08:00
parent 74ba7ea61b
commit 925058a5ae
58 changed files with 4391 additions and 3361 deletions
+66
View File
@@ -6,6 +6,8 @@ digraph refgraph_full {
"App/algo/adc_health.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/adc_input.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/adc_input.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/freq.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/freq.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/phasor.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/phasor.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/algo/prot_elm.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -22,10 +24,16 @@ digraph refgraph_full {
"App/cfg/cfg_file.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/cfg/cfg_file_store.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/cfg/cfg_file_store_mcu.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/cfg/prot_set_store.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/cfg/prot_set_store.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/fs/vol_lock.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/fs/vol_lock.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/console_log.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/console_log.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/soe_rec.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/soe_rec.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/soe_store.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/soe_store.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/sys_log.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/log/sys_log_mcu.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/protocol/iec104/iec104_asdu.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -67,6 +75,8 @@ digraph refgraph_full {
"Drivers/BSP/NET/lftpd/lftpd.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/BSP/NET/lftpd/lftpd_auth.c" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/BSP/NET/net_config.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NET/net_listener.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NET/net_listener.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NET/net_select.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NET/net_select.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NET/net_socket.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -203,12 +213,14 @@ digraph refgraph_full {
"test/host/adc_input/stub/tpafe5160.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/cfg_file/cfg_file_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/cfg_file/stub/dbg_log.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/freq/freq_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/ftp_auth/ftp_auth_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/iec104/iec104_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/phasor/phasor_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/prot_elm/prot_elm_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/prot_set/prot_set_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/sha256/sha256_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/soe/soe_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/sys_clock/stub/FreeRTOS.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/sys_clock/stub/dbg_log.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/sys_clock/stub/main.h" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -236,23 +248,30 @@ digraph refgraph_full {
"App/algo/adc_input.c" -> "Inc/adc.h" [color="#2563eb", penwidth=1.0];
"App/algo/adc_input.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/adc_input.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/freq.c" -> "App/algo/freq.h" [color="#2563eb", penwidth=1.0];
"App/algo/freq.c" -> "App/algo/phasor.h" [color="#2563eb", penwidth=1.0];
"App/algo/freq.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/freq.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/phasor.c" -> "App/algo/phasor.h" [color="#2563eb", penwidth=1.0];
"App/algo/phasor.c" -> "App/algo/sample_ring.h" [color="#2563eb", penwidth=1.0];
"App/algo/phasor.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/phasor.h" -> "App/algo/adc_input.h" [color="#2563eb", penwidth=1.0];
"App/algo/phasor.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/prot_elm.c" -> "App/algo/freq.h" [color="#2563eb", penwidth=1.0];
"App/algo/prot_elm.c" -> "App/algo/phasor.h" [color="#2563eb", penwidth=1.0];
"App/algo/prot_elm.c" -> "App/algo/prot_elm.h" [color="#2563eb", penwidth=1.0];
"App/algo/prot_elm.c" -> "App/algo/prot_set.h" [color="#2563eb", penwidth=1.0];
"App/algo/prot_elm.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/prot_elm.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/prot_set.c" -> "App/algo/prot_set.h" [color="#2563eb", penwidth=1.0];
"App/algo/prot_set.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/prot_set.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/sample_ring.c" -> "App/algo/sample_ring.h" [color="#2563eb", penwidth=1.0];
"App/algo/sample_ring.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/algo/sample_ring.h" -> "App/algo/adc_input.h" [color="#2563eb", penwidth=1.0];
"App/algo/sample_ring.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/app/app_main.c" -> "App/algo/adc_input.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "App/algo/freq.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "App/algo/phasor.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "App/algo/prot_elm.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "App/algo/prot_set.h" [color="#2563eb", penwidth=1.0];
@@ -285,6 +304,11 @@ digraph refgraph_full {
"App/cfg/cfg_file_store_mcu.c" -> "Lib/FatFs/ff.h" [color="#94a3b8", penwidth=0.7];
"App/cfg/cfg_file_store_mcu.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
"App/cfg/cfg_file_store_mcu.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/cfg/prot_set_store.c" -> "App/algo/prot_set.h" [color="#2563eb", penwidth=1.0];
"App/cfg/prot_set_store.c" -> "App/cfg/cfg_file_store.h" [color="#2563eb", penwidth=1.0];
"App/cfg/prot_set_store.c" -> "App/cfg/prot_set_store.h" [color="#2563eb", penwidth=1.0];
"App/cfg/prot_set_store.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"App/cfg/prot_set_store.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/fs/vol_lock.c" -> "App/fs/vol_lock.h" [color="#2563eb", penwidth=1.0];
"App/fs/vol_lock.c" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h" [color="#94a3b8", penwidth=0.7];
"App/fs/vol_lock.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
@@ -296,8 +320,22 @@ digraph refgraph_full {
"App/log/console_log.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [color="#94a3b8", penwidth=0.7];
"App/log/console_log.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/console_log.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/soe_rec.c" -> "App/log/soe_rec.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_rec.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/soe_rec.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/soe_store.c" -> "App/fs/vol_lock.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_store.c" -> "App/log/soe_rec.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_store.c" -> "App/log/soe_store.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_store.c" -> "App/util/sys_clock.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_store.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_store.c" -> "Lib/FatFs/ff.h" [color="#94a3b8", penwidth=0.7];
"App/log/soe_store.c" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h" [color="#94a3b8", penwidth=0.7];
"App/log/soe_store.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/soe_store.h" -> "App/log/soe_rec.h" [color="#2563eb", penwidth=1.0];
"App/log/soe_store.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/sys_log.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/sys_log_mcu.c" -> "App/fs/vol_lock.h" [color="#2563eb", penwidth=1.0];
"App/log/sys_log_mcu.c" -> "App/log/soe_store.h" [color="#2563eb", penwidth=1.0];
"App/log/sys_log_mcu.c" -> "App/log/sys_log.h" [color="#2563eb", penwidth=1.0];
"App/log/sys_log_mcu.c" -> "App/util/sys_clock.h" [color="#2563eb", penwidth=1.0];
"App/log/sys_log_mcu.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
@@ -330,6 +368,7 @@ digraph refgraph_full {
"App/sync/time_sync.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/task/adc_task.c" -> "App/algo/adc_health.h" [color="#2563eb", penwidth=1.0];
"App/task/adc_task.c" -> "App/algo/adc_input.h" [color="#2563eb", penwidth=1.0];
"App/task/adc_task.c" -> "App/algo/freq.h" [color="#2563eb", penwidth=1.0];
"App/task/adc_task.c" -> "App/algo/phasor.h" [color="#2563eb", penwidth=1.0];
"App/task/adc_task.c" -> "App/algo/sample_ring.h" [color="#2563eb", penwidth=1.0];
"App/task/adc_task.c" -> "App/log/console_log.h" [color="#2563eb", penwidth=1.0];
@@ -342,6 +381,7 @@ digraph refgraph_full {
"App/task/adc_task.c" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h" [color="#94a3b8", penwidth=0.7];
"App/task/default_task.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/cfg/cfg_file.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/cfg/prot_set_store.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/fs/vol_lock.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/log/sys_log.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/task/default_task.h" [color="#2563eb", penwidth=1.0];
@@ -356,6 +396,7 @@ digraph refgraph_full {
"App/task/default_task.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
"App/task/default_task.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [color="#94a3b8", penwidth=0.7];
"App/task/default_task.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"App/task/iec104_serv.c" -> "App/algo/prot_set.h" [color="#2563eb", penwidth=1.0];
"App/task/iec104_serv.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"App/task/iec104_serv.c" -> "App/protocol/iec104/iec104_link.h" [color="#2563eb", penwidth=1.0];
"App/task/iec104_serv.c" -> "App/protocol/iec104/iec104_pts.h" [color="#2563eb", penwidth=1.0];
@@ -382,6 +423,8 @@ digraph refgraph_full {
"App/task/protect_task.c" -> "App/algo/adc_health.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "App/algo/prot_elm.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "App/algo/prot_set.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "App/log/console_log.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "App/log/soe_store.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "App/task/protect_task.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "App/util/task_watchdog.h" [color="#2563eb", penwidth=1.0];
"App/task/protect_task.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
@@ -450,6 +493,18 @@ digraph refgraph_full {
"Drivers/BSP/GD5F2GQ5UE/nand_ftl.c" -> "Lib/dhara/nand.h" [color="#94a3b8", penwidth=0.7];
"Drivers/BSP/GD5F2GQ5UE/nand_ftl.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/GD5F2GQ5UE/nand_ftl.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/NET/net_listener.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "Drivers/BSP/NET/net_listener.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h" [color="#94a3b8", penwidth=0.7];
"Drivers/BSP/NET/net_listener.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
"Drivers/BSP/NET/net_listener.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [color="#94a3b8", penwidth=0.7];
"Drivers/BSP/NET/net_listener.c" -> "test/board/net_latency_test.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_listener.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/NET/net_listener.h" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_select.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_select.c" -> "Drivers/BSP/NET/net_select.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_select.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
@@ -461,6 +516,7 @@ digraph refgraph_full {
"Drivers/BSP/NET/net_select.h" -> "Drivers/BSP/NET/net_config.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_select.h" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/NET/net_listener.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NET/net_socket.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
@@ -703,6 +759,7 @@ digraph refgraph_full {
"test/board/ch395f_reuse_bench.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_reuse_bench.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_reuse_bench.c" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h" [color="#94a3b8", penwidth=0.7];
"test/board/ch395f_reuse_bench.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_reuse_bench.c" -> "test/board/ch395f_reuse_bench.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_test.h" -> "test/board/test_common.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_test.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
@@ -797,9 +854,14 @@ digraph refgraph_full {
"test/host/cfg_file/cfg_file_test.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"test/host/cfg_file/cfg_file_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/cfg_file/stub/dbg_log.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/freq/freq_test.c" -> "App/algo/freq.c" [color="#2563eb", penwidth=1.0];
"test/host/freq/freq_test.c" -> "App/algo/phasor.c" [color="#2563eb", penwidth=1.0];
"test/host/freq/freq_test.c" -> "App/algo/sample_ring.c" [color="#2563eb", penwidth=1.0];
"test/host/freq/freq_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/ftp_auth/ftp_auth_test.c" -> "App/util/sha256.c" [color="#2563eb", penwidth=1.0];
"test/host/ftp_auth/ftp_auth_test.c" -> "Drivers/BSP/NET/lftpd/lftpd_auth.c" [color="#94a3b8", penwidth=0.7];
"test/host/ftp_auth/ftp_auth_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/iec104/iec104_test.c" -> "App/algo/freq.c" [color="#2563eb", penwidth=1.0];
"test/host/iec104/iec104_test.c" -> "App/algo/phasor.c" [color="#2563eb", penwidth=1.0];
"test/host/iec104/iec104_test.c" -> "App/algo/prot_elm.c" [color="#2563eb", penwidth=1.0];
"test/host/iec104/iec104_test.c" -> "App/algo/prot_set.c" [color="#2563eb", penwidth=1.0];
@@ -811,6 +873,7 @@ digraph refgraph_full {
"test/host/phasor/phasor_test.c" -> "App/algo/phasor.c" [color="#2563eb", penwidth=1.0];
"test/host/phasor/phasor_test.c" -> "App/algo/sample_ring.c" [color="#2563eb", penwidth=1.0];
"test/host/phasor/phasor_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/prot_elm/prot_elm_test.c" -> "App/algo/freq.c" [color="#2563eb", penwidth=1.0];
"test/host/prot_elm/prot_elm_test.c" -> "App/algo/phasor.c" [color="#2563eb", penwidth=1.0];
"test/host/prot_elm/prot_elm_test.c" -> "App/algo/prot_elm.c" [color="#2563eb", penwidth=1.0];
"test/host/prot_elm/prot_elm_test.c" -> "App/algo/prot_set.c" [color="#2563eb", penwidth=1.0];
@@ -820,6 +883,9 @@ digraph refgraph_full {
"test/host/prot_set/prot_set_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/sha256/sha256_test.c" -> "App/util/sha256.c" [color="#2563eb", penwidth=1.0];
"test/host/sha256/sha256_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/soe/soe_test.c" -> "App/log/soe_rec.c" [color="#2563eb", penwidth=1.0];
"test/host/soe/soe_test.c" -> "App/protocol/iec104/iec104_asdu.c" [color="#2563eb", penwidth=1.0];
"test/host/soe/soe_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/sys_clock/stub/FreeRTOS.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/sys_clock/stub/dbg_log.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/sys_clock/stub/main.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
+459 -5
View File
@@ -17,6 +17,14 @@
"path": "App/algo/adc_input.h",
"class": "fp"
},
{
"path": "App/algo/freq.c",
"class": "fp"
},
{
"path": "App/algo/freq.h",
"class": "fp"
},
{
"path": "App/algo/phasor.c",
"class": "fp"
@@ -81,6 +89,14 @@
"path": "App/cfg/cfg_file_store_mcu.c",
"class": "fp"
},
{
"path": "App/cfg/prot_set_store.c",
"class": "fp"
},
{
"path": "App/cfg/prot_set_store.h",
"class": "fp"
},
{
"path": "App/fs/vol_lock.c",
"class": "fp"
@@ -97,6 +113,22 @@
"path": "App/log/console_log.h",
"class": "fp"
},
{
"path": "App/log/soe_rec.c",
"class": "fp"
},
{
"path": "App/log/soe_rec.h",
"class": "fp"
},
{
"path": "App/log/soe_store.c",
"class": "fp"
},
{
"path": "App/log/soe_store.h",
"class": "fp"
},
{
"path": "App/log/sys_log.h",
"class": "fp"
@@ -301,6 +333,14 @@
"path": "Drivers/BSP/NET/net_config.h",
"class": "fp"
},
{
"path": "Drivers/BSP/NET/net_listener.c",
"class": "fp"
},
{
"path": "Drivers/BSP/NET/net_listener.h",
"class": "fp"
},
{
"path": "Drivers/BSP/NET/net_select.c",
"class": "fp"
@@ -5761,6 +5801,10 @@
"path": "test/host/cfg_file/stub/dbg_log.h",
"class": "fp"
},
{
"path": "test/host/freq/freq_test.c",
"class": "fp"
},
{
"path": "test/host/ftp_auth/ftp_auth_test.c",
"class": "fp"
@@ -5785,6 +5829,10 @@
"path": "test/host/sha256/sha256_test.c",
"class": "fp"
},
{
"path": "test/host/soe/soe_test.c",
"class": "fp"
},
{
"path": "test/host/sys_clock/stub/FreeRTOS.h",
"class": "fp"
@@ -5909,6 +5957,36 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/algo/freq.c",
"to": "App/algo/freq.h",
"kind": "fp-fp",
"include": "freq.h"
},
{
"from": "App/algo/freq.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "math.h"
},
{
"from": "App/algo/freq.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stddef.h"
},
{
"from": "App/algo/freq.c",
"to": "App/algo/phasor.h",
"kind": "fp-fp",
"include": "phasor.h"
},
{
"from": "App/algo/freq.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/algo/phasor.c",
"to": "App/algo/phasor.h",
@@ -5963,6 +6041,12 @@
"kind": "fp-fp",
"include": "phasor.h"
},
{
"from": "App/algo/prot_elm.c",
"to": "App/algo/freq.h",
"kind": "fp-fp",
"include": "freq.h"
},
{
"from": "App/algo/prot_elm.c",
"to": "外部/系统头文件",
@@ -5981,6 +6065,12 @@
"kind": "fp-fp",
"include": "prot_set.h"
},
{
"from": "App/algo/prot_set.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "App/algo/prot_set.h",
"to": "外部/系统头文件",
@@ -6089,6 +6179,12 @@
"kind": "fp-fp",
"include": "phasor.h"
},
{
"from": "App/app/app_main.c",
"to": "App/algo/freq.h",
"kind": "fp-fp",
"include": "freq.h"
},
{
"from": "App/app/app_main.c",
"to": "App/algo/prot_set.h",
@@ -6239,6 +6335,36 @@
"kind": "fp-fp",
"include": "vol_lock.h"
},
{
"from": "App/cfg/prot_set_store.c",
"to": "App/cfg/prot_set_store.h",
"kind": "fp-fp",
"include": "prot_set_store.h"
},
{
"from": "App/cfg/prot_set_store.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stddef.h"
},
{
"from": "App/cfg/prot_set_store.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "App/cfg/prot_set_store.c",
"to": "App/cfg/cfg_file_store.h",
"kind": "fp-fp",
"include": "cfg_file_store.h"
},
{
"from": "App/cfg/prot_set_store.c",
"to": "App/algo/prot_set.h",
"kind": "fp-fp",
"include": "prot_set.h"
},
{
"from": "App/fs/vol_lock.c",
"to": "App/fs/vol_lock.h",
@@ -6311,6 +6437,84 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/log/soe_rec.c",
"to": "App/log/soe_rec.h",
"kind": "fp-fp",
"include": "soe_rec.h"
},
{
"from": "App/log/soe_rec.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "App/log/soe_rec.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/log/soe_store.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "App/log/soe_store.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "App/log/soe_store.c",
"to": "App/log/soe_store.h",
"kind": "fp-fp",
"include": "soe_store.h"
},
{
"from": "App/log/soe_store.c",
"to": "App/log/soe_rec.h",
"kind": "fp-fp",
"include": "soe_rec.h"
},
{
"from": "App/log/soe_store.c",
"to": "Lib/FatFs/ff.h",
"kind": "fp-tp",
"include": "ff.h"
},
{
"from": "App/log/soe_store.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h",
"kind": "fp-tp",
"include": "cmsis_os.h"
},
{
"from": "App/log/soe_store.c",
"to": "App/util/sys_clock.h",
"kind": "fp-fp",
"include": "sys_clock.h"
},
{
"from": "App/log/soe_store.c",
"to": "App/fs/vol_lock.h",
"kind": "fp-fp",
"include": "vol_lock.h"
},
{
"from": "App/log/soe_store.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/log/soe_store.h",
"to": "App/log/soe_rec.h",
"kind": "fp-fp",
"include": "soe_rec.h"
},
{
"from": "App/log/sys_log.h",
"to": "外部/系统头文件",
@@ -6347,6 +6551,12 @@
"kind": "fp-fp",
"include": "sys_log.h"
},
{
"from": "App/log/sys_log_mcu.c",
"to": "App/log/soe_store.h",
"kind": "fp-fp",
"include": "soe_store.h"
},
{
"from": "App/log/sys_log_mcu.c",
"to": "Lib/FatFs/ff.h",
@@ -6545,6 +6755,12 @@
"kind": "fp-fp",
"include": "phasor.h"
},
{
"from": "App/task/adc_task.c",
"to": "App/algo/freq.h",
"kind": "fp-fp",
"include": "freq.h"
},
{
"from": "App/task/adc_task.c",
"to": "App/algo/adc_health.h",
@@ -6647,6 +6863,12 @@
"kind": "fp-fp",
"include": "cfg_file.h"
},
{
"from": "App/task/default_task.c",
"to": "App/cfg/prot_set_store.h",
"kind": "fp-fp",
"include": "prot_set_store.h"
},
{
"from": "App/task/default_task.c",
"to": "App/log/sys_log.h",
@@ -6707,6 +6929,12 @@
"kind": "fp-fp",
"include": "net_socket.h"
},
{
"from": "App/task/iec104_serv.c",
"to": "App/algo/prot_set.h",
"kind": "fp-fp",
"include": "prot_set.h"
},
{
"from": "App/task/iec104_serv.c",
"to": "App/util/sys_clock.h",
@@ -6845,6 +7073,12 @@
"kind": "fp-fp",
"include": "adc_health.h"
},
{
"from": "App/task/protect_task.c",
"to": "App/log/console_log.h",
"kind": "fp-fp",
"include": "console_log.h"
},
{
"from": "App/task/protect_task.c",
"to": "App/algo/prot_elm.h",
@@ -6857,6 +7091,12 @@
"kind": "fp-fp",
"include": "prot_set.h"
},
{
"from": "App/task/protect_task.c",
"to": "App/log/soe_store.h",
"kind": "fp-fp",
"include": "soe_store.h"
},
{
"from": "App/task/protect_task.c",
"to": "App/util/task_watchdog.h",
@@ -7301,6 +7541,84 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdbool.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h",
"kind": "fp-tp",
"include": "FreeRTOS.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/task.h",
"kind": "fp-tp",
"include": "task.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h",
"kind": "fp-tp",
"include": "cmsis_os.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Drivers/BSP/NET/net_socket.h",
"kind": "fp-fp",
"include": "net_socket.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Drivers/BSP/NET/net_listener.h",
"kind": "fp-fp",
"include": "net_listener.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Drivers/BSP/CH395F/ch395f.h",
"kind": "fp-fp",
"include": "ch395f.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "test/board/test_config.h",
"kind": "fp-fp",
"include": "test_config.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "test/board/net_latency_test.h",
"kind": "fp-fp",
"include": "net_latency_test.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Inc/main.h",
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "Drivers/BSP/NET/net_listener.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "Drivers/BSP/NET/net_listener.h",
"to": "Drivers/BSP/NET/net_types.h",
"kind": "fp-fp",
"include": "net_types.h"
},
{
"from": "Drivers/BSP/NET/net_select.c",
"to": "外部/系统头文件",
@@ -7409,6 +7727,12 @@
"kind": "fp-fp",
"include": "net_socket.h"
},
{
"from": "Drivers/BSP/NET/net_socket.c",
"to": "Drivers/BSP/NET/net_listener.h",
"kind": "fp-fp",
"include": "net_listener.h"
},
{
"from": "Drivers/BSP/NET/net_socket.c",
"to": "Drivers/BSP/CH395F/ch395f.h",
@@ -40043,6 +40367,12 @@
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "test/board/ch395f_reuse_bench.c",
"to": "Inc/main.h",
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "test/board/ch395f_reuse_bench.c",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
@@ -40655,6 +40985,42 @@
"kind": "fp-ext",
"include": "stdio.h"
},
{
"from": "test/host/freq/freq_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "math.h"
},
{
"from": "test/host/freq/freq_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/host/freq/freq_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdio.h"
},
{
"from": "test/host/freq/freq_test.c",
"to": "App/algo/sample_ring.c",
"kind": "fp-fp",
"include": "sample_ring.c"
},
{
"from": "test/host/freq/freq_test.c",
"to": "App/algo/phasor.c",
"kind": "fp-fp",
"include": "phasor.c"
},
{
"from": "test/host/freq/freq_test.c",
"to": "App/algo/freq.c",
"kind": "fp-fp",
"include": "freq.c"
},
{
"from": "test/host/ftp_auth/ftp_auth_test.c",
"to": "外部/系统头文件",
@@ -40721,6 +41087,12 @@
"kind": "fp-fp",
"include": "phasor.c"
},
{
"from": "test/host/iec104/iec104_test.c",
"to": "App/algo/freq.c",
"kind": "fp-fp",
"include": "freq.c"
},
{
"from": "test/host/iec104/iec104_test.c",
"to": "App/algo/prot_elm.c",
@@ -40769,6 +41141,12 @@
"kind": "fp-fp",
"include": "phasor.c"
},
{
"from": "test/host/prot_elm/prot_elm_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "math.h"
},
{
"from": "test/host/prot_elm/prot_elm_test.c",
"to": "外部/系统头文件",
@@ -40799,6 +41177,12 @@
"kind": "fp-fp",
"include": "phasor.c"
},
{
"from": "test/host/prot_elm/prot_elm_test.c",
"to": "App/algo/freq.c",
"kind": "fp-fp",
"include": "freq.c"
},
{
"from": "test/host/prot_elm/prot_elm_test.c",
"to": "App/algo/prot_elm.c",
@@ -40841,6 +41225,36 @@
"kind": "fp-fp",
"include": "sha256.c"
},
{
"from": "test/host/soe/soe_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/host/soe/soe_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdio.h"
},
{
"from": "test/host/soe/soe_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "test/host/soe/soe_test.c",
"to": "App/log/soe_rec.c",
"kind": "fp-fp",
"include": "soe_rec.c"
},
{
"from": "test/host/soe/soe_test.c",
"to": "App/protocol/iec104/iec104_asdu.c",
"kind": "fp-fp",
"include": "iec104_asdu.c"
},
{
"from": "test/host/sys_clock/sys_clock_test.c",
"to": "外部/系统头文件",
@@ -41146,6 +41560,13 @@
"App/algo/adc_input.h": [
"stdint.h"
],
"App/algo/freq.c": [
"math.h",
"stddef.h"
],
"App/algo/freq.h": [
"stdint.h"
],
"App/algo/phasor.c": [
"math.h",
"stddef.h"
@@ -41159,6 +41580,9 @@
"App/algo/prot_elm.h": [
"stdint.h"
],
"App/algo/prot_set.c": [
"string.h"
],
"App/algo/prot_set.h": [
"stdint.h"
],
@@ -41188,6 +41612,9 @@
"App/cfg/cfg_file_store_mcu.c": [
"string.h"
],
"App/cfg/prot_set_store.c": [
"stddef.h"
],
"App/fs/vol_lock.h": [
"stdint.h"
],
@@ -41198,6 +41625,18 @@
"stdint.h",
"stdio.h"
],
"App/log/soe_rec.c": [
"string.h"
],
"App/log/soe_rec.h": [
"stdint.h"
],
"App/log/soe_store.c": [
"string.h"
],
"App/log/soe_store.h": [
"stdint.h"
],
"App/log/sys_log.h": [
"stddef.h",
"stdint.h"
@@ -41320,6 +41759,10 @@
"Drivers/BSP/NET/lftpd/private/lftpd_io.h": [
"stdint.h"
],
"Drivers/BSP/NET/net_listener.c": [
"stdbool.h",
"string.h"
],
"Drivers/BSP/NET/net_select.c": [
"stdio.h",
"string.h"
@@ -42963,6 +43406,11 @@
"test/host/cfg_file/stub/dbg_log.h": [
"stdio.h"
],
"test/host/freq/freq_test.c": [
"math.h",
"stdint.h",
"stdio.h"
],
"test/host/ftp_auth/ftp_auth_test.c": [
"stdio.h",
"string.h"
@@ -42978,6 +43426,7 @@
"stdio.h"
],
"test/host/prot_elm/prot_elm_test.c": [
"math.h",
"stdint.h",
"stdio.h"
],
@@ -42989,6 +43438,11 @@
"stdio.h",
"string.h"
],
"test/host/soe/soe_test.c": [
"stdint.h",
"stdio.h",
"string.h"
],
"test/host/sys_clock/stub/FreeRTOS.h": [
"stdint.h"
],
@@ -43047,11 +43501,11 @@
]
},
"stats": {
"files_indexed": 1479,
"includes_parsed": 5878,
"resolved": 4749,
"unresolved": 1129,
"nodes_fp": 199,
"files_indexed": 1491,
"includes_parsed": 5939,
"resolved": 4792,
"unresolved": 1147,
"nodes_fp": 211,
"nodes_tp": 1266,
"ext_names": 351
},
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 474 KiB

After

Width:  |  Height:  |  Size: 506 KiB

+20 -20
View File
@@ -30,7 +30,7 @@ digraph refgraph_module {
"外部/系统头文件" [shape=ellipse, fillcolor="#fef3c7", color="#d97706"];
"App" -> "App/log" [color="#2563eb", penwidth=1.2, label="×2"];
"App" -> "App/util" [color="#2563eb", penwidth=1.2, label="×4"];
"App" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×3"];
"App" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×4"];
"App" -> "BSP/GD5F2GQ5UE" [color="#2563eb", penwidth=1.2, label="×1"];
"App" -> "BSP/NET" [color="#2563eb", penwidth=1.2, label="×1"];
"App" -> "BSP/RS485" [color="#2563eb", penwidth=1.2, label="×1"];
@@ -40,16 +40,16 @@ digraph refgraph_module {
"App" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×3"];
"App" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×5"];
"App" -> "test" [color="#2563eb", penwidth=1.2, label="×1"];
"App" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×24"];
"App/log" -> "App" [color="#2563eb", penwidth=1.2, label="×2"];
"App/log" -> "App/util" [color="#2563eb", penwidth=1.2, label="×1"];
"App/log" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"App/log" -> "FatFs" [color="#2563eb", penwidth=1.2, label="×1"];
"App/log" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×4"];
"App" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×28"];
"App/log" -> "App" [color="#2563eb", penwidth=1.2, label="×3"];
"App/log" -> "App/util" [color="#2563eb", penwidth=1.2, label="×2"];
"App/log" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×2"];
"App/log" -> "FatFs" [color="#2563eb", penwidth=1.2, label="×2"];
"App/log" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×5"];
"App/log" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×1"];
"App/log" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
"App/task" -> "App" [color="#2563eb", penwidth=1.2, label="×15"];
"App/task" -> "App/log" [color="#2563eb", penwidth=1.2, label="×4"];
"App/log" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×8"];
"App/task" -> "App" [color="#2563eb", penwidth=1.2, label="×18"];
"App/task" -> "App/log" [color="#2563eb", penwidth=1.2, label="×6"];
"App/task" -> "App/util" [color="#2563eb", penwidth=1.2, label="×12"];
"App/task" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×7"];
"App/task" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×1"];
@@ -82,12 +82,12 @@ digraph refgraph_module {
"BSP/GD5F2GQ5UE" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/GD5F2GQ5UE" -> "dhara" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/GD5F2GQ5UE" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
"BSP/NET" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/NET" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NET" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×8"];
"BSP/NET" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NET" -> "test" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NET" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×3"];
"BSP/NET" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NET" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×3"];
"BSP/NET" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×11"];
"BSP/NET" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×3"];
"BSP/NET" -> "test" [color="#2563eb", penwidth=1.2, label="×4"];
"BSP/NET" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
"BSP/NETCFG" -> "App" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NETCFG" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/NETCFG" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
@@ -158,8 +158,8 @@ digraph refgraph_module {
"simulator" -> "SDL2" [color="#2563eb", penwidth=1.2, label="×2"];
"simulator" -> "test" [color="#2563eb", penwidth=1.2, label="×1"];
"simulator" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×6"];
"test" -> "App" [color="#2563eb", penwidth=1.2, label="×23"];
"test" -> "App/log" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "App" [color="#2563eb", penwidth=1.2, label="×29"];
"test" -> "App/log" [color="#2563eb", penwidth=1.2, label="×2"];
"test" -> "App/task" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "App/util" [color="#2563eb", penwidth=1.2, label="×7"];
"test" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×11"];
@@ -171,7 +171,7 @@ digraph refgraph_module {
"test" -> "FatFs" [color="#2563eb", penwidth=1.2, label="×3"];
"test" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×10"];
"test" -> "HAL" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×6"];
"test" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×7"];
"test" -> "lftpd" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×41"];
"test" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×43"];
}
+20 -20
View File
@@ -53,7 +53,7 @@
<title>App&#45;&gt;BSP(公共)</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M148.05,-1387.62C152.12,-1329.94 164.75,-1147.35 172.75,-996 175.25,-948.69 173.45,-614.1 190.75,-570 232.46,-463.65 420.17,-456.58 452.25,-447 562.85,-413.96 594.71,-409.24 710,-415 899.15,-424.46 1382.24,-429.46 1558,-500 1580.77,-509.14 1589.92,-512.37 1601.5,-534 1626.53,-580.76 1611.96,-719.5 1619.5,-772 1626,-817.29 1637.87,-869.03 1645.63,-900.6"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1642.13,-901.04 1647.94,-909.89 1648.92,-899.35 1642.13,-901.04"/>
<text xml:space="preserve" text-anchor="middle" x="852" y="-425.59" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
<text xml:space="preserve" text-anchor="middle" x="852" y="-425.59" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
</g>
<!-- BSP/GD5F2GQ5UE -->
<g id="node7" class="node">
@@ -183,42 +183,42 @@
<title>App&#45;&gt;外部/系统头文件</title>
<path fill="none" stroke="#94a3b8" stroke-width="0.7" d="M147.41,-1424.07C152.88,-1574.84 190.75,-2618 190.75,-2618 219.33,-2680.06 346.47,-2702 364.5,-2702 364.5,-2702 364.5,-2702 1772.25,-2702 1886.5,-2702 1913.77,-1780.11 1917.95,-1603.17"/>
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="0.7" points="1921.44,-1603.31 1918.17,-1593.23 1914.45,-1603.15 1921.44,-1603.31"/>
<text xml:space="preserve" text-anchor="middle" x="982" y="-2706.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×24</text>
<text xml:space="preserve" text-anchor="middle" x="982" y="-2706.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×28</text>
</g>
<!-- App/log&#45;&gt;App -->
<g id="edge14" class="edge">
<title>App/log&#45;&gt;App</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M246.34,-1108.41C228.72,-1161.05 178.02,-1312.55 156.55,-1376.72"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="153.32,-1375.33 153.47,-1385.92 159.96,-1377.55 153.32,-1375.33"/>
<text xml:space="preserve" text-anchor="middle" x="197.5" y="-1271.67" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
<text xml:space="preserve" text-anchor="middle" x="197.5" y="-1271.67" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
</g>
<!-- App/log&#45;&gt;App/util -->
<g id="edge15" class="edge">
<title>App/log&#45;&gt;App/util</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M284.99,-1086.43C295.17,-1084.62 306.42,-1081.94 316.25,-1078 320.77,-1076.19 325.33,-1073.93 329.74,-1071.47"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="331.38,-1074.57 338.14,-1066.41 327.76,-1068.58 331.38,-1074.57"/>
<text xml:space="preserve" text-anchor="middle" x="309.5" y="-1087.1" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×1</text>
<text xml:space="preserve" text-anchor="middle" x="309.5" y="-1087.1" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
</g>
<!-- App/log&#45;&gt;BSP(公共) -->
<g id="edge16" class="edge">
<title>App/log&#45;&gt;BSP(公共)</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M281.69,-1071.65C282.85,-1070.19 283.89,-1068.63 284.75,-1067 322.31,-995.75 263.92,-775.56 302.75,-705 393.55,-540.01 530.74,-645.71 710,-588 791.88,-561.64 793.86,-512.73 876.75,-489.75 909.94,-480.55 919.86,-487.85 954.25,-489.75 1087.97,-497.15 1120.63,-508.34 1253.75,-523 1408.29,-540.02 1492.87,-449.76 1601.5,-561 1617.94,-577.83 1616.04,-748.73 1619.5,-772 1626.22,-817.26 1638.03,-869.01 1645.72,-900.59"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1642.22,-901.01 1648.01,-909.89 1649.01,-899.34 1642.22,-901.01"/>
<text xml:space="preserve" text-anchor="middle" x="915.5" y="-494.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×1</text>
<text xml:space="preserve" text-anchor="middle" x="915.5" y="-494.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
</g>
<!-- App/log&#45;&gt;FatFs -->
<g id="edge17" class="edge">
<title>App/log&#45;&gt;FatFs</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M280.87,-1071.61C282.27,-1070.13 283.58,-1068.6 284.75,-1067 310.07,-1032.44 380.39,-729.59 414.75,-704 487.91,-649.52 1154.04,-660.44 1235.75,-701 1271.26,-718.62 1300.43,-754.67 1317.75,-780.04"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1314.79,-781.9 1323.22,-788.32 1320.64,-778.05 1314.79,-781.9"/>
<text xml:space="preserve" text-anchor="middle" x="793.38" y="-671.71" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×1</text>
<text xml:space="preserve" text-anchor="middle" x="793.38" y="-671.71" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
</g>
<!-- App/log&#45;&gt;FreeRTOS -->
<g id="edge18" class="edge">
<title>App/log&#45;&gt;FreeRTOS</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M254.41,-1108.31C253.91,-1246.67 253.93,-2124.43 334.25,-2206 448.09,-2321.62 925.29,-2321.59 1063.75,-2237 1078.01,-2228.29 1069.46,-2215.31 1081.75,-2204 1214.41,-2081.93 1343.31,-2183.26 1452.75,-2040 1485.22,-1997.49 1456.15,-1972.46 1470.75,-1921 1477.8,-1896.15 1489.5,-1869.28 1498.86,-1849.66"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1501.88,-1851.44 1503.12,-1840.92 1495.59,-1848.38 1501.88,-1851.44"/>
<text xml:space="preserve" text-anchor="middle" x="852" y="-2296.31" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
<text xml:space="preserve" text-anchor="middle" x="852" y="-2296.31" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×5</text>
</g>
<!-- App/log&#45;&gt;Src/Inc (CubeMX) -->
<g id="edge19" class="edge">
@@ -232,7 +232,7 @@
<title>App/log&#45;&gt;外部/系统头文件</title>
<path fill="none" stroke="#94a3b8" stroke-width="0.7" d="M281.71,-1071.66C282.87,-1070.19 283.9,-1068.64 284.75,-1067 307.82,-1022.72 294.99,-667.32 302.75,-618 310.85,-566.5 314.8,-553.38 334.25,-505 362.47,-434.8 353.02,-398.75 414.75,-355 534.67,-270.01 601.2,-347.82 741.5,-304 945.09,-240.42 960.21,-95 1173.5,-95 1173.5,-95 1173.5,-95 1772.25,-95 1923.56,-95 1920.43,-1336.76 1918.85,-1544.65"/>
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="0.7" points="1915.35,-1544.48 1918.77,-1554.51 1922.35,-1544.54 1915.35,-1544.48"/>
<text xml:space="preserve" text-anchor="middle" x="1036.75" y="-140.8" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
<text xml:space="preserve" text-anchor="middle" x="1036.75" y="-140.8" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×8</text>
</g>
<!-- App/task -->
<g id="node3" class="node">
@@ -245,14 +245,14 @@
<title>App/task&#45;&gt;App</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M759.49,-1429.25C695.29,-1427.86 548.76,-1425 425.5,-1425 252.5,-1425 252.5,-1425 252.5,-1425 229.69,-1425 204.53,-1420.66 184.48,-1416.13"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="185.33,-1412.74 174.79,-1413.82 183.71,-1419.55 185.33,-1412.74"/>
<text xml:space="preserve" text-anchor="middle" x="424.5" y="-1429.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×15</text>
<text xml:space="preserve" text-anchor="middle" x="424.5" y="-1429.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×18</text>
</g>
<!-- App/task&#45;&gt;App/log -->
<g id="edge22" class="edge">
<title>App/task&#45;&gt;App/log</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M783.99,-1411.51C765.81,-1374.54 719.01,-1295 652.75,-1295 364.5,-1295 364.5,-1295 364.5,-1295 285.85,-1295 263.21,-1176.41 256.87,-1119.62"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="260.39,-1119.6 255.91,-1110 253.42,-1120.3 260.39,-1119.6"/>
<text xml:space="preserve" text-anchor="middle" x="495.12" y="-1299.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
<text xml:space="preserve" text-anchor="middle" x="495.12" y="-1299.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×6</text>
</g>
<!-- App/task&#45;&gt;App/util -->
<g id="edge23" class="edge">
@@ -513,42 +513,42 @@
<title>BSP/NET&#45;&gt;BSP(公共)</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1366.53,-1486.55C1371.67,-1482.5 1376.67,-1477.93 1380.75,-1473 1392.66,-1458.6 1385.78,-1448.45 1398.75,-1435 1417.52,-1415.54 1429.05,-1420 1452.75,-1407 1500.15,-1380.99 1523.25,-1387.42 1558,-1346 1607.98,-1286.42 1639.96,-1044.77 1649.85,-959.76"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1653.31,-960.31 1650.97,-949.97 1646.36,-959.51 1653.31,-960.31"/>
<text xml:space="preserve" text-anchor="middle" x="1514.38" y="-1402.27" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×1</text>
<text xml:space="preserve" text-anchor="middle" x="1514.38" y="-1402.27" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
</g>
<!-- BSP/NET&#45;&gt;BSP/CH395F -->
<g id="edge56" class="edge">
<title>BSP/NET&#45;&gt;BSP/CH395F</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1368.34,-1486.5C1373.07,-1482.56 1377.45,-1478.04 1380.75,-1473 1400.78,-1442.42 1375.3,-1420.79 1398.75,-1392.75 1415.45,-1372.77 1430.53,-1383.59 1452.75,-1370 1464.34,-1362.91 1476.01,-1353.7 1485.97,-1345.06"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1488.11,-1347.85 1493.24,-1338.57 1483.45,-1342.62 1488.11,-1347.85"/>
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1397.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1397.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
</g>
<!-- BSP/NET&#45;&gt;FreeRTOS -->
<g id="edge57" class="edge">
<title>BSP/NET&#45;&gt;FreeRTOS</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1349.64,-1523.41C1371.83,-1556.12 1418.69,-1626.84 1452.75,-1689.75 1471.36,-1724.13 1489.77,-1765.26 1501.33,-1792.2"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1498,-1793.31 1505.13,-1801.14 1504.44,-1790.57 1498,-1793.31"/>
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1694.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×8</text>
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1694.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×11</text>
</g>
<!-- BSP/NET&#45;&gt;Src/Inc (CubeMX) -->
<g id="edge58" class="edge">
<title>BSP/NET&#45;&gt;Src/Inc (CubeMX)</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1329.66,-1523.2C1315.28,-1572.05 1276.62,-1702.66 1273.25,-1706 1264.72,-1714.47 870.58,-1827.86 858.75,-1830 813.65,-1838.17 762.2,-1841.67 722,-1843.13"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="722.14,-1839.63 712.26,-1843.45 722.37,-1846.62 722.14,-1839.63"/>
<text xml:space="preserve" text-anchor="middle" x="982" y="-1802.03" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
<text xml:space="preserve" text-anchor="middle" x="982" y="-1802.03" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
</g>
<!-- BSP/NET&#45;&gt;test -->
<g id="edge59" class="edge">
<title>BSP/NET&#45;&gt;test</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1312.76,-1523.36C1301.62,-1531.38 1287.5,-1539.88 1273.25,-1544 1205.85,-1563.5 1123.02,-1554.62 1075.71,-1546.64"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1076.44,-1543.21 1065.99,-1544.91 1075.22,-1550.1 1076.44,-1543.21"/>
<text xml:space="preserve" text-anchor="middle" x="1174.5" y="-1560.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
<text xml:space="preserve" text-anchor="middle" x="1174.5" y="-1560.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
</g>
<!-- BSP/NET&#45;&gt;外部/系统头文件 -->
<g id="edge60" class="edge">
<title>BSP/NET&#45;&gt;外部/系统头文件</title>
<path fill="none" stroke="#94a3b8" stroke-width="0.7" d="M1369.51,-1508.86C1459.7,-1519.56 1714.78,-1549.82 1843.01,-1565.03"/>
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="0.7" points="1842.5,-1568.5 1852.84,-1566.2 1843.32,-1561.55 1842.5,-1568.5"/>
<text xml:space="preserve" text-anchor="middle" x="1654.12" y="-1550.91" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
<text xml:space="preserve" text-anchor="middle" x="1654.12" y="-1550.91" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
</g>
<!-- BSP/NETCFG&#45;&gt;App -->
<g id="edge61" class="edge">
@@ -1081,14 +1081,14 @@
<title>test&#45;&gt;App</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1009.38,-1553.16C1003.72,-1555.58 997.65,-1557.72 991.75,-1559 941.77,-1569.83 927.56,-1560.8 876.75,-1555 673.39,-1531.78 630.18,-1463 425.5,-1463 252.5,-1463 252.5,-1463 252.5,-1463 224,-1463 195.8,-1446.76 175.69,-1431.62"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="177.95,-1428.94 167.93,-1425.48 173.6,-1434.43 177.95,-1428.94"/>
<text xml:space="preserve" text-anchor="middle" x="565.75" y="-1483.23" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×23</text>
<text xml:space="preserve" text-anchor="middle" x="565.75" y="-1483.23" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×29</text>
</g>
<!-- test&#45;&gt;App/log -->
<g id="edge132" class="edge">
<title>test&#45;&gt;App/log</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1023.99,-1520.88C979.4,-1453.18 819.14,-1219 735.75,-1219 364.5,-1219 364.5,-1219 364.5,-1219 312.49,-1219 279.87,-1157.53 264.65,-1119.45"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="267.95,-1118.29 261.13,-1110.18 261.41,-1120.77 267.95,-1118.29"/>
<text xml:space="preserve" text-anchor="middle" x="651.75" y="-1223.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×1</text>
<text xml:space="preserve" text-anchor="middle" x="651.75" y="-1223.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
</g>
<!-- test&#45;&gt;App/task -->
<g id="edge133" class="edge">
@@ -1172,7 +1172,7 @@
<title>test&#45;&gt;Src/Inc (CubeMX)</title>
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1028.81,-1557.21C1021.26,-1575.76 1008.06,-1604.86 991.75,-1627 978.05,-1645.6 973.32,-1649.96 954.25,-1663 862.74,-1725.57 818.71,-1704.27 728,-1768 708.31,-1781.83 689.28,-1801.13 675.28,-1816.82"/>
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="672.78,-1814.36 668.84,-1824.2 678.05,-1818.96 672.78,-1814.36"/>
<text xml:space="preserve" text-anchor="middle" x="852" y="-1718.37" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×6</text>
<text xml:space="preserve" text-anchor="middle" x="852" y="-1718.37" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×7</text>
</g>
<!-- test&#45;&gt;lftpd -->
<g id="edge145" class="edge">
@@ -1186,7 +1186,7 @@
<title>test&#45;&gt;外部/系统头文件</title>
<path fill="none" stroke="#94a3b8" stroke-width="0.7" d="M1055.68,-1557.25C1080.06,-1580.02 1126.08,-1616 1173.5,-1616 1173.5,-1616 1173.5,-1616 1772.25,-1616 1807.2,-1616 1845.1,-1604.79 1873.56,-1593.95"/>
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="0.7" points="1874.59,-1597.31 1882.61,-1590.39 1872.03,-1590.8 1874.59,-1597.31"/>
<text xml:space="preserve" text-anchor="middle" x="1514.38" y="-1620.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×41</text>
<text xml:space="preserve" text-anchor="middle" x="1514.38" y="-1620.75" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×43</text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

+5 -5
View File
@@ -4,7 +4,7 @@
<text x="26" y="62" font-size="13" fill="#64748b">左列 = 根因(比喻 + 机制);右列 = N34 阶段 0~3 的对应拆法;环与环叠加才构成「玄学」</text>
<rect x="26" y="78.0" width="690" height="120.0" rx="10" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
<text x="42" y="100.0" font-size="15" font-weight="700" fill="#e11d48">① 门铃会丢</text>
<text x="136" y="100.0" font-size="11.5" fill="#64748b">Trap 22:丢中断</text>
<text x="136" y="100.0" font-size="11.5" fill="#64748b">TRAP-NET-17:丢中断</text>
<rect x="42" y="110.0" width="422" height="34" rx="7" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="253" y="131.6" font-size="13" fill="#1e293b" text-anchor="middle">症状:UI 复连永久拒连、FTP 第 29 轮自己死掉,全程不报一个错</text>
<rect x="42" y="152.0" width="481" height="34" rx="7" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
@@ -20,7 +20,7 @@
<text x="780" y="130.0" font-size="11" fill="#64748b" text-anchor="middle">拆掉</text>
<rect x="26" y="238.0" width="690" height="120.0" rx="10" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
<text x="42" y="260.0" font-size="15" font-weight="700" fill="#e11d48">② 门铃总闸看一眼就清零</text>
<text x="226" y="260.0" font-size="11.5" fill="#64748b">Trap 25 B3</text>
<text x="226" y="260.0" font-size="11.5" fill="#64748b">TRAP-NET-20 B3</text>
<rect x="42" y="270.0" width="395" height="34" rx="7" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="239" y="291.6" font-size="13" fill="#1e293b" text-anchor="middle">症状:别的 Socket 的 CONNECT/DISCONNECT 从此永久失踪</text>
<rect x="42" y="312.0" width="505" height="34" rx="7" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
@@ -107,8 +107,8 @@
<text x="283" y="1133.5" font-size="13" fill="#1e293b" text-anchor="middle">机制:芯片 close 只发 FIN;对端内核存活但应用挂死 = 回 ACK 却不起身</text>
<rect x="844" y="1038.0" width="690" height="120.0" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
<text x="860" y="1060.0" font-size="15" font-weight="700" fill="#16a34a">知道他不轰人,就别指望他轰人</text>
<rect x="860" y="1070.0" width="441" height="34" rx="7" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1080" y="1091.5" font-size="13" fill="#1e293b" text-anchor="middle">定案写入 Trap 07:兜底靠查岗(keepalive/TIMEOUT),不靠 RST</text>
<rect x="860" y="1070.0" width="471" height="34" rx="7" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1095" y="1091.5" font-size="13" fill="#1e293b" text-anchor="middle">定案写入 TRAP-NET-07:兜底靠查岗(keepalive/TIMEOUT),不靠 RST</text>
<rect x="860" y="1112.0" width="415" height="34" rx="7" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1067" y="1133.5" font-size="13" fill="#1e293b" text-anchor="middle">net_drop_conn 本地拆除 + reconciler 形态③④ 认领僵尸态</text>
<line x1="722" y1="1098.0" x2="834" y2="1098.0" stroke="#94a3b8" stroke-width="2.5"/>
@@ -151,5 +151,5 @@
<rect x="844" y="1344" width="690" height="78" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.5"/>
<text x="860" y="1372" font-size="15" font-weight="700" fill="#16a34a">逐环拆掉 = 稳(N34 阶段 0~3 已实施)</text>
<text x="860" y="1396" font-size="12" fill="#334155">不依赖门铃 · 唯一主人 · 永不冻结 · 100ms 对账 · 开张前排渣——台架回归全绿</text>
<text x="26" y="1452" font-size="12" fill="#94a3b8">证据链:docs/问题记录/CH395F_Trap_Records.md(Trap 07/09/14/17/22/23/25) · docs/网络/N34-自愈审计/实验方案.md(E1~E13) · 修复落地见 docs/网络/N34-自愈审计/实施方案.md</text>
<text x="26" y="1452" font-size="12" fill="#94a3b8">证据链:docs/问题记录/CH395F_Trap_Records.md(TRAP-NET-07/-09/-12/-17/-18/-20) · docs/网络/N34-自愈审计/实验方案.md(E1~E13) · 修复落地见 docs/网络/N34-自愈审计/实施方案.md</text>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 230,104 B (224.71 KB) | 1,048,576 B (1024 KB) | 21.9% |
| ER_IROM1 | 230,216 B (224.82 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,208 B (225.8 KB)** | 1,048,576 B (1024 KB) | **22.0%** |
| **Flash (ROM 合计)** | **231,320 B (225.9 KB)** | 1,048,576 B (1024 KB) | **22.1%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
+1 -1
View File
@@ -15,7 +15,7 @@
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---|
| ER_IROM1 (Flash) | 230,104 B | 1,048,576 B | 21.9% | 参考 |
| ER_IROM1 (Flash) | 230,216 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 |
@@ -3,7 +3,7 @@
> 版本 v1.1 日期 2026-09-21 作者 AI 辅助调研
> 数据来源:`data/ram_report_20260921.md`(.map 静态占用)+ 逐行代码核验(`nand_ftl.c` / `gd5f2gq5ue.c` / `Lib/dhara` / `Lib/FatFs/ffconf.h` / `lftpd*`)
> 定位:`RAM使用分析与优化方案.md` 的存储专项补充;优化项编号 P0/P1/P2,与 `docs/内存管理/RAM使用分析与优化方案.md` §6 的 S 序号在"规划"一节对齐
> v1.1 清理(2026-09-21 同日):移除已落地的 P0-1 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F Trap 05 / 错误码传播审计),以及过期逐模块基线表;仅保留未实施项与回归要求
> v1.1 清理(2026-09-21 同日):移除已落地的 P0-1 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05 / 错误码传播审计),以及过期逐模块基线表;仅保留未实施项与回归要求
## 1. 现状约束(不变,是下述全部方案的依据)
@@ -25,7 +25,7 @@
| 断开后芯片行为由 FUN_PARA bit3 分流:=0 芯片自动关 Socket(→`0x00/0x00`);**=1 芯片什么都不做(→`0x05/0x00`),MCU 必须自己 CLOSE** | `docs/芯片手册/CH395F.md` §5.23 L572、§5.52 L795② |
⚠ **本仓 `ch395f_set_fun_para(0x08)`(`Drivers/BSP/NET/net_socket.c:275`,bit3 SOCKET_CLOSE=1,
Trap 18/25 定案为保留排空现场)⇒ 断开后芯片必然停在 `0x05/0x00`。
TRAP-NET-15/-20 定案为保留排空现场)⇒ 断开后芯片必然停在 `0x05/0x00`。
B10 探针看到的"孤儿芯片态"不是芯片异常,而是本仓这个配置的**直接且合法的后果**。**
⚠ 口径错误待修:`Drivers/BSP/NET/net_socket.c:3627` 与 `:3641` 的函数头注释写
@@ -60,11 +60,11 @@ B10 探针看到的"孤儿芯片态"不是芯片异常,而是本仓这个配
|---|---|---|
| 孤儿监听(B10) | 边沿没人接,四方互等 | 对账④兜底 ≈1 s(已提交 `4f2cc8b`) |
| 消费者卡死 | ④/D10 抢先 relisten → recv 永久 `WOULDBLOCK` → `g_client` 不清、新连接被旧会话静默吸收 | **④ 扩大了此窗口**,未修 |
| 监听被误关 | `net_can_read` 在非 ESTABLISHED(含 LISTENING)返 1 → recv 得 `WOULDBLOCK` → `Drivers/BSP/UI/ui_net_sink.c:563` 无护栏 `net_close(g_client)` = 关掉监听 fd | 靠随后 `bind_listen` 自救,落 Trap 09 沉降窗 |
| 监听被误关 | `net_can_read` 在非 ESTABLISHED(含 LISTENING)返 1 → recv 得 `WOULDBLOCK` → `Drivers/BSP/UI/ui_net_sink.c:563` 无护栏 `net_close(g_client)` = 关掉监听 fd | 靠随后 `bind_listen` 自救,落 TRAP-NET-09 沉降窗 |
另有一条**探针失效**:`net_can_accept`(`:1453`)要求 ESTABLISHED 才返 1,
而 `Drivers/BSP/UI/ui_net_sink.c:512` 用它门控 accept → :7003 **永不进 `net_accept`**
→ Trap 25 的 `net_lost_connect_probe`(`:1250-1289`)对这个端口**从不运行**,
→ TRAP-NET-20 的 `net_lost_connect_probe`(`:1250-1289`)对这个端口**从不运行**,
丢 CONNECT 只剩对账②。:21/:8888 因为无条件轮询 accept,探针才活着。
### 2.4 FUN_PARA bit3=1 的必要性与安全边界(2026-10-12 评估,结论:**必须保留 0x08**)
@@ -81,15 +81,15 @@ B10 探针看到的"孤儿芯片态"不是芯片异常,而是本仓这个配
- `docs/芯片手册/CH395F.md` §5.23 L572 同向:「为 0 则……主动将 Socket 状态置为关闭状态**并将相关缓冲区全部清除**;
反之则不会对 Socket 状态和相关缓冲区做任何操作,以方便外部单片机……**将残留的数据读出**。」
实测现场(Trap 25 三次定案的判别探针):`st=0x05/0x00` 且 `recv_len=1027`——
实测现场(TRAP-NET-20 三次定案的判别探针):`st=0x05/0x00` 且 `recv_len=1027`——
Socket 仍 OPEN、芯片 TCP 已 CLOSED、**RX 缓冲里 STOR 载荷一个不少**。这正是 bit3=1 按规格
保留现场,不是芯片异常(`docs/问题记录/CH395F_Trap_Records.md` Trap 25「定案二」)。
保留现场,不是芯片异常(`docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-20「定案二」)。
**② bit3=0 的代价是「硬件保证的数据丢失」,且不可在软件层补救。**
本产品最典型的流量形态是「发完即 FIN」:ftplib `storbinary` 语义 = `sendall` 发完立即关
数据连接、回控制通道等 226。芯片把最后一段数据落进 RX 的同时抛出 DISCONNECT:
- bit3=1:RX 数据在,MCU 来得及读(Trap 23 的「排空 RX 再 close」修复才成立);
- bit3=1:RX 数据在,MCU 来得及读(TRAP-NET-18 的「排空 RX 再 close」修复才成立);
- bit3=0:芯片**当场**关 Socket 并清缓冲 → MCU 永远读不到尾部 → 文件被截断却仍回报 226
(静默损坏)。这是 bit3=0 的**必然结果**,不是概率问题——软件再聪明也拿不回已被硬件擦掉的数据。
@@ -97,7 +97,7 @@ Socket 仍 OPEN、芯片 TCP 已 CLOSED、**RX 缓冲里 STOR 载荷一个不少
严格更差。:8888 对时包、:7003 远显推流同样是「数据 + 随即关闭」形态,一并受害。
**③ bit3=0 也并不能真正消除孤儿态。** 芯片自动关以**收到** DISCONNECT/TIM_OUT 为前提,
而 Trap 22 已实证快速复连场景芯片会**丢**这两个中断(`docs/问题记录/CH395F_Trap_Records.md`
而 TRAP-NET-17 已实证快速复连场景芯片会**丢**这两个中断(`docs/问题记录/CH395F_Trap_Records.md`
B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket 照样存在。
它只是降低出现概率,代价却是上面第②条。
@@ -113,8 +113,8 @@ 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) |
| 关闭姿势必须正确 | Trap 14/17:陈旧 DISCONNECT 锁存秒断下一条连接 | `net_close_locked` 既有「close → 等 `CLOSED` → 排空本 Socket 中断」,是 bit3=1 下唯一正确的关闭序列 |
| 异常释放须先排空残渣再关 | Trap 23:~2KB RX 残渣被下条连接当自己的数据读走 | `net_relisten_locked` 隔离期(400 ms)持续排空 RX + 主动 close + 等 `CLOSED/TCP_CLOSED` |
| 关闭姿势必须正确 | 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」机制本体不变
> (`NET_MSG_DONTWAIT` 仍是 `net_recv_locked` 的非阻塞路径与 EOF 判据载体),但对外
@@ -139,7 +139,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
本产品有 21/8888/7003 **三个常驻监听** + 会话期动态 PASV 监听。
2. 池分配按**源端口匹配**(`docs/芯片手册/CH395F.md` §9.2.6 L1295),多监听器的池会互相串。
3. 历史实测:「多连接模式下最多接受 3 个客户端(Socket 1~3),Socket 4~7 的 CONNECT 中断从不触发」(:10)、
「多个 Socket 数据互相覆盖」(:35)——即 Trap 02/11。
「多个 Socket 数据互相覆盖」(:35)——即 TRAP-NET-02/-11。
且 FUN_PARA **初始化后不可改**(`docs/芯片手册/CH395F.md` §5.52 L795⑥),这是开机一次性决策,不能运行时降级。
**结论:A′ 出局;根治必须在单连接复用模型内做。**
@@ -152,7 +152,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
其它所有位置只**置事实**,不**做动作**。(N34 阶段 3 起该决定者 = `net_reconcile_tick` 形态③④,pump 已并入,见 §10.10)
两条不变量成立后,"谁重建""会不会错过"这两类问题在结构上消失,
对账④从"唯一救命稻草"降级为"事件丢失时的补漏"(Trap 22 实证事件会丢,故对账**必须保留**)。
对账④从"唯一救命稻草"降级为"事件丢失时的补漏"(TRAP-NET-17 实证事件会丢,故对账**必须保留**)。
## 5. 分阶段方案
@@ -180,7 +180,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
(LISTENING 或 CLOSED+auto_relisten)→ 让 :7003 重新吃到 `lost_connect_probe`。
4. 验收(新增腿,专打"抢先 relisten"):客户端连上 :7003 → 拖住 uiTask 使 relisten 先发生 →
断连 → 断言 sink 仍拿到 EOF、且**下一个客户端能被服务**(当前会被旧会话静默吸收)。
回归矩阵沿用 B10 那套(`:7003` 10/10、`:21` 50/50×2、`:8888` 20/20、`ftp_regression` 11/11、Trap 23 5/5×2)。
回归矩阵沿用 B10 那套(`:7003` 10/10、`:21` 50/50×2、`:8888` 20/20、`ftp_regression` 11/11、TRAP-NET-18 5/5×2)。
### P2 单一所有者(中改,消灭多所有者)—— P2a ✅ 2026-10-12 / P2b 建议暂缓
@@ -209,7 +209,7 @@ B10 行)→ 中断丢了,bit3=0 同样停在已关/半关态,孤儿 Socket
P0 → P1 → P2 严格串行,每阶段独立提交、独立回归、可单独回滚。
P1 是性价比最高的一步:它不改变 fd 模型,却把 B10 与"消费者卡死"两类事故同时从结构上消掉。
P2 之后,对账④保留但只作事件丢失的补漏(Trap 22 实证事件会丢,不可删)。
P2 之后,对账④保留但只作事件丢失的补漏(TRAP-NET-17 实证事件会丢,不可删)。
## 7. 证据附录(2026-10-12 实测与调研,供后续执行时复核)
@@ -227,7 +227,7 @@ P2 之后,对账④保留但只作事件丢失的补漏(Trap 22 实证事件
- **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 22 实证事件会丢)。
- **结论**:单连接模式下官方**零自动重监听**,且原生做法是**在事件现场当场重建**;本仓对账④是周期兜底,方向一致但不可互替(TRAP-NET-17 实证事件会丢)。
- **中断所有权**:全局寄存器整体读即清、无按位清除(`docs/芯片手册/CH395F.md` §5.15 L441);CONNECT/DISCONNECT/TIM_OUT 是**事件位**(可丢)、RECV 是**条件位**(可补)→ 任何"只靠事件重建监听"的方案天然不完备。
### 7.3 状态机事实(`Drivers/BSP/NET/net_socket.c` 3921 行)
@@ -242,7 +242,7 @@ P2 之后,对账④保留但只作事件丢失的补漏(Trap 22 实证事件
| 服务 | 认为的连接结束信号 | net 层实际给什么 | 监听重建者 | 是否留无人重建组合 |
|---|---|---|---|---|
| `lftpd` 控制 :21 | `read_line!=0`(EOF→-1,`lftpd_inet.c:154-156`) | 阻塞 pending 保证 CLOSED 必了结(FIN→0 / 异常→CONNRESET,`net_socket.c:2457-2462`) | net 层(`:2449-2451`),应用**永不** close 控制 fd(`lftpd.c:746-750`) | 无(契约一致,是正面样板) |
| `time_sync` :8888 | EOF/30 s 预算/错误,随后**无条件** `net_close(conn==ls)`(`time_sync.c:182`) | 同上 | 应用自己每会话全量重建(`:224`) | 不留孤儿,但违反「EOF 不得 close 监听 fd」→ close→重建沉降窗(Trap 09)+ 最坏 ~10 s 端口下线 |
| `time_sync` :8888 | EOF/30 s 预算/错误,随后**无条件** `net_close(conn==ls)`(`time_sync.c:182`) | 同上 | 应用自己每会话全量重建(`:224`) | 不留孤儿,但违反「EOF 不得 close 监听 fd」→ close→重建沉降窗(TRAP-NET-09)+ 最坏 ~10 s 端口下线 |
| `ui_net_sink` :7003 | `net_recv` 返 0 或真错误;重建条件 `net_get_sock(g_listen)==NULL`(`:557`/`:566`) | 该 0 只在 recv 时刻 `state==CLOSED` 才给 | 名义 net 层,实际自身条件在 MCU **恒假**(fd 不释放) | **有**(B10 与"消费者卡死"都出自这里) |
| 模拟器后端 | `recv` 0 恒可得、fd 分离(`net_socket_sim.c:293-336`) | Winsock 原生 | 无需 | 无——且对上述全部失效模式**零覆盖** |
@@ -251,16 +251,16 @@ P2 之后,对账④保留但只作事件丢失的补漏(Trap 22 实证事件
`net_can_read:1477-1478` 在非 ESTABLISHED(**含 LISTENING**)返 1 → 消费者去 recv →
DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-569` 判为"真错误"→
`:563` **无 `g_client != g_listen` 护栏**的 `net_close(g_client)` 在 MCU 上等于**关掉监听 fd**
(与同文件 `:134` 注释"严禁"自相矛盾)→ 靠随后 `bind_listen` 自救,落 Trap 09 沉降窗。
(与同文件 `:134` 注释"严禁"自相矛盾)→ 靠随后 `bind_listen` 自救,落 TRAP-NET-09 沉降窗。
## 8. 决策记录
| # | 决定 | 依据 |
|---|---|---|
| D1 | **A′(启用芯片多连接模式)出局**,根治必须在单连接复用模型内做 | `docs/问题记录/CH395F_Trap_Records.md` 附 FTP 改造清单第 4 条「多连接模式不支持多监听」;本产品 3 常驻监听 + 动态 PASV;§9.2.6 L1295 池按源端口匹配会互串;历史实测 Trap 02/11;FUN_PARA 初始化后不可改(`docs/芯片手册/CH395F.md` §5.52 L795⑥) |
| 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 22 实证事件会丢 → 周期对账不可删;④ 已把 `:7003` 从 0/10 永久死拉到 10/10 |
| D4 | `FUN_PARA` **保持 `0x08`**,不因孤儿态改配置(2026-10-12 复核确认) | bit3=1 是 Trap 18/25 定案的"保留排空现场"契约;bit3=0 会令芯片在 DISCONNECT/TIM_OUT 时**当场清缓冲**(`docs/芯片手册/CH395F.md` §5.52② / §5.23 L572)→「发完即 FIN」流量的尾部数据必被硬件擦除、**不可补救**;且 bit3=0 也挡不住丢中断(Trap 22)→ 孤儿态照样有。孤儿态是 bit3=1 的合法后果,应在软件层收口而非回退契约。完整论证见 §2.4 |
| 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 |
| D5 | 顺序 **P0 → P1 → P2**,每阶段独立提交、独立回归、可单独回滚 | P1 不动 fd 模型却消掉两类事故,性价比最高 |
| D6 | 真机/模拟器**双模型分裂暂不消除** | 消除即等于做 P3;先靠 P1 把应用侧契约对齐 |
| D7 | 同族残留**不在本次根治范围**,各自单列:U1(CONNECTING 滞留,B2 残余)、U11、U12、U14、U15,以及审计项 B4/B5/B6/B7/B8/B9 | 与监听生命周期不同因,混做会让回归面失控 |
@@ -290,9 +290,9 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56
|---|---|---|
| `@test` 启动闭环 | **PASS** | 启动锚点齐、无 `[ERR]`、reset cause ×1、7.2 s 到 net ready |
| `ftp_regression` | **11/11** | 含 STOR 32KB —— 「发完即 FIN」链路,`session_dead` 直接作用面 |
| Trap 23 `--check-abort`(RST) | **5/5** | 450 + 5xx 正确回报,无误报 226 |
| Trap 23 `--graceful`(FIN) | **5/5** | 226 正常 |
| `:21` 快速复连 0 ms ×50 | **50/50** | Trap 22 基线不回归 |
| TRAP-NET-18 `--check-abort`(RST) | **5/5** | 450 + 5xx 正确回报,无误报 226 |
| TRAP-NET-18 `--graceful`(FIN) | **5/5** | 226 正常 |
| `:21` 快速复连 0 ms ×50 | **50/50** | TRAP-NET-17 基线不回归 |
| `:21` 快速复连 100 ms ×50 | **50/50** | 同上 |
| `:8888` 0 ms ×20 | **20/20** | 对时服务不回归 |
| `:7003` 300 ms ×10 | **10/10** | B10 原始失败场景(修复前 0/10) |
@@ -314,7 +314,7 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56
200 ms + 去抖(`NET_RECONCILE_STABLE_N`),`ui_net_sink` 轮询 100 ms ——
正常时序下消费者总是先 recv 到 EOF,对账抢不到。**只有 uiTask 被拖住**才可能出现,
而那是设备内部状态。
- 处置:按 Trap 25「不留未证明的复杂度」**删除该探针**;该场景登记为**残余验证项**,
- 处置:按 TRAP-NET-20「不留未证明的复杂度」**删除该探针**;该场景登记为**残余验证项**,
需设备内注入点(参照 `TEST_LATENCY_PROBE` 的编译开关模式)才能在真机复现。
- 因此 P1 的**正确性依据**是:① 代码级论证(判据由边沿改电平、置位/清零完备、
两处 relisten 补状态门);② 上表全量回归矩阵**不回归**;③ 不变量 I 的结构性论证。
@@ -345,7 +345,7 @@ RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作
- [ ] **P2b(同步忙等改造)—— 建议暂缓**:`net_close_locked` 的 500 ms `osDelay` 循环、
`net_listen_locked` 的 3×500 ms。**实测依据**:全量历史 `task stall` 在忙等未改造时
同样存在(P1 基线同条件 53 条),忙等的危害是**理论性**的;而 `net_close` 是每次会话
结束都走的热路径。按 Trap 25「不留未证明的复杂度」,为一个未发生的故障去动热路径,
结束都走的热路径。按 TRAP-NET-20「不留未证明的复杂度」,为一个未发生的故障去动热路径,
性价比存疑 —— 建议等有 `task stall` 与忙等的**因果证据**后再做。
(**N34 阶段 3 已实施 2026-10-13**:不是"为忙等而改",而是驱动 `sock_reset` 原语落地后
顺路收口——`net_sock_reset_sync` 确定性排渣替代盲等(GRACEFUL 100ms 底 / 600ms 封顶),
@@ -583,5 +583,5 @@ void net_listener_tick(void);
GRACEFUL 100ms 底 / 600ms 封顶),并删除 close/connect 路径的全局中断读清(F-NET-7)。
**台架回归(2026-10-13,最终构建 Code=211348)**:`:7003` 50/50、`:21` 100ms/0ms/600ms 各 50/50、
Trap 23 RST 10/10 + FIN 5/5、Trap 25 n30 11/11、iec104 18/18、`ftp_regression` 11/11、E-02 双 PASS;
TRAP-NET-18 RST 10/10 + FIN 5/5、TRAP-NET-20 n30 11/11、iec104 18/18、`ftp_regression` 11/11、E-02 双 PASS;
netTask 静态最坏链 1944 B 不变(链尾改经 `net_listener_retry_tick`→`net_relisten_locked`)。
@@ -569,7 +569,7 @@ if (nand_ftl_format() == 0) {
11. **FA_OPEN_APPEND 陷阱 / 短写** — `FA_OPEN_APPEND`(0x30) = `FA_OPEN_ALWAYS|FA_SEEKEND`,**不含 `FA_WRITE`(0x02)**——不带写位打开的文件 `f_write` 恒返回 `FR_DENIED`(7)(这是当年 `/log` 写失败的真根因);追加写必须写 `FA_WRITE | FA_OPEN_APPEND`(2026-09-20 台架实测,`Lib/FatFs` 移植版)。该移植的 `f_write` 在簇分配失败时返回 `FR_OK` + 短写(**没有错误码**)→ 判失败必须校验写回的字节数。标志位为**旧式编号**:`FA_READ`=0x01 / `FA_WRITE`=0x02 / `FA_CREATE_NEW`=0x04 / `FA_CREATE_ALWAYS`=0x08 / `FA_OPEN_ALWAYS`=0x10 / `FA_OPEN_APPEND`=0x30 / `FA_SEEKEND`=0x20(`ff.c` 内部定义),与上游 FatFs R0.14 不同,**勿照抄上游代码示例**。
12. **崩溃可观测性(勿屏蔽)** — 已开启 `configCHECK_FOR_STACK_OVERFLOW=2` + `configUSE_MALLOC_FAILED_HOOK` + HardFault 现场报告(`Src/stm32f4xx_it.c`:裸 UART 打印 PC/LR/xPSR + CFSR/HFSR/BFAR/MMFAR 后复位,对照 `.map` 定位符号)。defaultTask **1024** words、testTask **2048** words 是**实测下限,勿降**。开机日志出现 `[ERR] STACK OVERFLOW task=` / `[ERR] HARDFAULT ...` / `MALLOC FAILED` 是精确定位线索,禁止屏蔽或降级。
13. **BBT 恢复模式** — 运行期坏块会随 TC-STO-03 注入累积;当介质被**误标脏**(如 DMA 误打 CCM 栈导致随机块被写坏并持久化)时,打开 `test/board/storage_test.h` 的 `ENABLE_STORAGE_RECOVERY_TESTS` 编译**恢复构建**,testTask 会执行 `gd5f2gq5ue_bbt_rebuild()`(出厂扫描清除运行期误标)+ `nand_ftl_format()` 清账后复位。恢复后容量回到基线(即扣除 BBT 保留池后的正常容量,非出厂裸容量)。
14. **lftpd 路径口径(归档更正 2026-10-13:原误记于 `docs/问题记录/CH395F_Trap_Records.md` Trap 12,移入本节)** — FTP STOR/CWD 曾频发 `FR_INVALID_NAME`(6):真约束即上条第 10 项的严格 8.3。原记录"FatFS 不接受 `/` 前缀 / 空字符串失败"两条与仓内 `Lib/FatFs/ff.c` 矛盾(`ff.c:3101-3109` 会剥前导分隔符、`:3127-3129` 空名返回根目录),**已删**。现行口径:`lftpd_io.c` 的 `to_fatfs_path()` 剥 `/` 前缀(行为正确,保留)+ 本仓路径用 `"0:"` 卷前缀(`FF_FS_RPATH=0` 下 `"."` 不是合法路径段)。长文件名需 `FF_USE_LFN=1` + `FF_LFN_UNICODE`(显著增 RAM,未启用)。
14. **lftpd 路径口径(归档更正 2026-10-13:原误记于 `docs/问题记录/CH395F_Trap_Records.md`——非 CH395F 议题,移入本节)** — FTP STOR/CWD 曾频发 `FR_INVALID_NAME`(6):真约束即上条第 10 项的严格 8.3。原记录"FatFS 不接受 `/` 前缀 / 空字符串失败"两条与仓内 `Lib/FatFs/ff.c` 矛盾(`ff.c:3101-3109` 会剥前导分隔符、`:3127-3129` 空名返回根目录),**已删**。现行口径:`lftpd_io.c` 的 `to_fatfs_path()` 剥 `/` 前缀(行为正确,保留)+ 本仓路径用 `"0:"` 卷前缀(`FF_FS_RPATH=0` 下 `"."` 不是合法路径段)。长文件名需 `FF_USE_LFN=1` + `FF_LFN_UNICODE`(显著增 RAM,未启用)。
### 10.2 FTP 服务(lftpd)鉴权语义(2026-10-11,N-19)
@@ -22,7 +22,7 @@
2. **魔法延时**:`ftpTask` 用 `osDelay(7000)` 硬等 PHY/网络稳定。
### 1.3 改造前的痛点(即引入 Event Flags 的动机)
- **魔法延时脆弱**:PHY 协商、DHCP、首轮 `net_poll()` 的耗时随硬件/环境波动;7s 是拍脑袋定值,一旦网络就绪变慢就失败。这正是 `docs/问题记录/CH395F_Trap_Records.md` 中记录的坑(Trap 09 子类:上电 PHY 未稳定即 listen)。
- **魔法延时脆弱**:PHY 协商、DHCP、首轮 `net_poll()` 的耗时随硬件/环境波动;7s 是拍脑袋定值,一旦网络就绪变慢就失败。这正是 `docs/问题记录/CH395F_Trap_Records.md` 中记录的坑(TRAP-NET-09 子类:上电 PHY 未稳定即 listen)。
- **轮询浪费节拍**:`start_test_task` 每 100ms 被唤醒一次去读一个全局变量,纯属空转。
- **依赖隐式且分散**:谁依赖谁、何时就绪,散落在多个任务的延时与 `if` 里,没有统一视图,新增子系统极易遗漏同步。
- **不可组合**:原全局标志只能表达"一个"条件;若某任务需"网络 **且** 存储都就绪"就无从表达。
+34 -35
View File
@@ -2,7 +2,7 @@
## 1. 概述
本文档定义 CH395F 以太网驱动(STM32F407ZGTx,SPI2 接口)的测试规范。当前固件 `FUN_PARA=0x08`(bit3 SOCKET_CLOSE=1,bit0/bit1 均为 0):8 个 Socket 0~7 全部可用(**由 Socket 索引 `_SN` 命令族 + 缓冲显式分配实现,与 FUN_PARA 无关**,见 `问题记录/CH395F_Trap_Records.md` Trap 18「定案更正」),Socket 0 作 TCP Server 单连接,覆盖单连接与多并发客户端场景。测试按阶段(TM-PHY-NN)组织,每个阶段包含独立测试用例(TC-NET-NNN)。
本文档定义 CH395F 以太网驱动(STM32F407ZGTx,SPI2 接口)的测试规范。当前固件 `FUN_PARA=0x08`(bit3 SOCKET_CLOSE=1,bit0/bit1 均为 0):8 个 Socket 0~7 全部可用(**由 Socket 索引 `_SN` 命令族 + 缓冲显式分配实现,与 FUN_PARA 无关**,见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15),Socket 0 作 TCP Server 单连接,覆盖单连接与多并发客户端场景。测试按阶段(TM-PHY-NN)组织,每个阶段包含独立测试用例(TC-NET-NNN)。
### 设计原则
@@ -25,11 +25,11 @@
| **单次 SPI 最大载荷** | **4096B** | `CH395F_SPI_DMA_MAX_PAYLOAD` |
| recv_len 最大值 | 65535 | `uint16_t` |
> **关键约束**:本固件每个 Socket 接收缓冲为 **2KB**(4 块 × 512B),发送缓冲为 **1KB**(2 块)。`recv_len` 在调 `read_recv_buf` 读取**部分**数据后归零(Trap 13),故每次接收应一次性读空当前可用数据(≤2KB)。`CH395F_SPI_DMA_BUF_SIZE=4100` 足以覆盖单 Socket 完整 2KB 接收缓冲。
> **关键约束**:本固件每个 Socket 接收缓冲为 **2KB**(4 块 × 512B),发送缓冲为 **1KB**(2 块)。旧"部分读后 `recv_len` 归零"机理**未证实且与 `docs/芯片手册/CH395F.md` §5.34 相左**(2026-09-23 复核降级),但**保守按"一次读空"执行**(≤2KB)。`CH395F_SPI_DMA_BUF_SIZE=2052` 恰好覆盖单 Socket 完整 2KB 接收缓冲。
### 多 Socket / 8 端口并发分配
`net_init` 中 `ch395f_set_fun_para(0x08)` 之后,循环 0~7 调用 `SET_RECV_BUF` / `SET_SEND_BUF` 重分配(**这一步才是 Socket 1~7 可用的前提**,见 `问题记录/CH395F_Trap_Records.md` Trap 18「定案更正」;必须在 `OPEN_SOCKET` 之前,顺序遵循 Trap 01:先数据 Socket 1~7 再监听 Socket 0):
`net_init` 中 `ch395f_set_fun_para(0x08)` 之后,循环 0~7 调用 `SET_RECV_BUF` / `SET_SEND_BUF` 重分配(**这一步才是 Socket 1~7 可用的前提**,见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15;必须在 `OPEN_SOCKET` 之前,顺序遵循 TRAP-NET-01:先数据 Socket 1~7 再监听 Socket 0):
| Socket | 接收起始块 | 接收块数 | 接收大小 | 发送起始块 | 发送块数 | 发送大小 |
|--------|-----------|---------|---------|-----------|---------|---------|
@@ -66,7 +66,7 @@ UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设使用同一布局。
**边界条件**:
- 发送 `≤1KB`:一次写发送缓冲完成(受发送缓冲 1KB 限制)
- 接收缓冲满(2KB):必须一次性读空,否则 `recv_len` 归零导致剩余数据丢失(Trap 13)
- 接收缓冲满(2KB):保守按一次性读空执行(旧"归零"机理未证实,见 `CH395F_Trap_Records.md` 台架纪律)
- 发送 `0B`:`net_send` 返回 `-1`(`NET_ERR_INVAL`)
> **注**:当前 `net_send` 的分块上限常量 `NET_DMA_MAX_PAYLOAD=4096` 大于单 Socket 发送缓冲(1KB),单次 `WRITE_SEND_BUF` 写入量受发送缓冲大小约束;大文件传输(Phase 6/8/10)需在硬件上实测确认无溢出。
@@ -227,7 +227,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
---
### TC-NET-204(硬件层): TCP 关闭次序 A/B/C 对照(Trap 07 定案用例)
### TC-NET-204(硬件层): TCP 关闭次序 A/B/C 对照(TRAP-NET-07 定案用例)
| 字段 | 值 |
|------|-----|
@@ -243,12 +243,12 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
| 模式 | 关闭序列 | 成功标准 | 实测(2026-10-11,5/5) |
|----|------|----------|----------|
| A | `ch395f_close_socket()` | 观察窗 10 s 内 `GET_SOCKET_STATUS` **byte0** 回到 `0x00` | **0 / 0 / 150 / 0 / 0 ms**;过渡态可见 `TIME_WAIT`(`0x0A`),**无 RST 语义** |
| B | `ch395f_tcp_disconnect()` → `ch395f_close_socket()` | 同上 | **全部 +0 ms**,两字节均 `0x00` → Trap 07 的"卡 `FIN_WAIT_2` 数分钟"**不成立** |
| B | `ch395f_tcp_disconnect()` → `ch395f_close_socket()` | 同上 | **全部 +0 ms**,两字节均 `0x00` → TRAP-NET-07 的"卡 `FIN_WAIT_2` 数分钟"**不成立** |
| C | `ch395f_tcp_disconnect()`(**不** close) | **预期相反**:10 s 内 byte0 保持 `0x05`(`docs/芯片手册/CH395F.md` §5.52 ② bit3=1 芯片不自动关) | byte0 恒 `0x05`、byte1 `0x00`;直接 `open_socket` → **`0x20` FAIL**(5/5,即历史 `Phase 2 open_socket: 0x20` 签名);补 `close` 后 +0 ms 回 CLOSED |
**判据口径**:`GET_SOCKET_STATUS_SN` 输出 2 字节——byte0 只有 `00H/05H`,byte1 才是 TCP 态且**仅在 byte0=OPEN 时有意义**(`docs/芯片手册/CH395F.md` §5.22 + 官方 SDK 注释);关闭与重开之间**不主动清中断**,残留事件由重开后首次中断读取现场暴露(本轮 15 轮均未观察到残留 `DISCONNECT`)。
**通过标准**:45/45。任一模式偏离上表 → 说明芯片关闭语义或 `FUN_PARA` 配置变了,须回查 `问题记录/CH395F_Trap_Records.md` Trap 07「定案更正」。
**通过标准**:45/45。任一模式偏离上表 → 说明芯片关闭语义或 `FUN_PARA` 配置变了,须回查 `问题记录/CH395F_Trap_Records.md` TRAP-NET-07。
**覆盖原则**:DP-03(状态机边界:关闭次序 × Socket 复用)
@@ -265,7 +265,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
**分层说明**:本阶段属于**硬件层模式**(netTask 自挂起),仅用 `ch395f_*` 裸命令验证芯片 UDP 能力:UDP Socket 打开、数据报接收、**包头源地址解析**与按源地址动态回显。
> CH395F UDP 接收帧格式(`ch395f_read_recv_buf` 返回):[0-1] 保留|[2-3] 源端口(大端)|[4-7] 源 IP|[8+] 载荷。回显前须以此源地址重设目标,且每次发送后等 `SENDBUF_FREE` 确认(Trap 04)。
> CH395F UDP 接收帧格式(`ch395f_read_recv_buf` 返回):[0-1] 保留|[2-3] 源端口(大端)|[4-7] 源 IP|[8+] 载荷。回显前须以此源地址重设目标,且每次发送后等 `SENDBUF_FREE` 确认(TRAP-NET-04)。
---
@@ -339,7 +339,7 @@ CH395F PC (dhcp_server)
**关键实现约束**:
- **xid/chaddr 回填**:OFFER/ACK 必须原样回传 DISCOVER 的 `xid([4:8])` 与 `chaddr([28:34])`(Trap 03:早期 xid 偏移错误导致芯片丢弃响应)
- **xid/chaddr 回填**:OFFER/ACK 必须原样回传 DISCOVER 的 `xid([4:8])` 与 `chaddr([28:34])`(TRAP-NET-03:早期 xid 偏移错误导致芯片丢弃响应)
- **option53 位置**:报文类型在 `data[242]`(236B 固定头 + magic `63825363` + option 偏移),`len<=250` 视为无效包
- **双广播发送**:OFFER/ACK 同时发 `255.255.255.255` 与 `192.168.1.255`(规避 Windows 定向广播路由限制)
- **REQUEST 重试**:PC 最多等 8×5s;期间收到重复 DISCOVER 则重发 OFFER
@@ -350,7 +350,7 @@ CH395F PC (dhcp_server)
|----|------|------|
| 1 | `ch395f_set_dhcp(1)` 启动协商 | - |
| 2 | 轮询 `GET_DHCP_STATUS == 0x00` | 超时 30s,间隔 500ms(循环中同步读清 GINT_ALL) |
| 3 | `GET_IP_INF` 轮询至连续 3 次一致(间隔 300ms,上限 8s)后打印 | 连续一致且非 `0.0.0.0`(滞后窗口实测 ~1 s,见 Trap 15) |
| 3 | `GET_IP_INF` 轮询至连续 3 次一致(间隔 300ms,上限 8s)后打印 | 连续一致且非 `0.0.0.0`(滞后窗口实测 ~1 s,见 TRAP-NET-13) |
| 4 | Socket 6(UDP,本地 :60000,send/recv buf 各 512B)向 `255.255.255.255:60001` 广播 4B 本机 IP 宣告 | SENBUF_FREE 等待 2s |
| 5 | 同 Socket 收 PC `"HELLO"`(5B),按包头源地址回显一次后关闭 | 窗口 30s(`PHASE4_HELLO_WAIT_MS`) |
@@ -415,7 +415,7 @@ CH395F PC (dhcp_server)
| **出口** | 串口 "Phase 5: N/N PASSED (failed=0)" + 吞吐统计 |
| **注意** | 与 NET 层阶段 7 同主题不同层:本阶段用 `ch395f_*` 裸命令直驱芯片,不经 net_socket/netTask |
> 编号说明:阶段 5 用例 **501/502**(顺序双测向完整性 + 变总量合规);原 PHY 测试(旧编号 901/902)因板载拓扑不可测已删除(见 Trap 05)。
> 编号说明:阶段 5 用例 **501/502**(顺序双测向完整性 + 变总量合规);原 PHY 测试(旧编号 901/902)因板载拓扑不可测已删除(见 TRAP-NET-05)。
### 为什么 2KB 接收缓冲不会丢数据(TCP 流控原理)
@@ -663,17 +663,17 @@ sequenceDiagram
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 发单包 4096B(实际单 Socket 接收缓冲为 2KB,固件分两次 `net_recv` 取空) | `net_recv` 累计收满 4096B(分次) | 接收字节数 ≠ 4096(丢失,见 TRAP-13) |
| 1 | PC 发单包 4096B(实际单 Socket 接收缓冲为 2KB,固件分两次 `net_recv` 取空) | `net_recv` 累计收满 4096B(分次) | 接收字节数 ≠ 4096(丢失,见台架纪律注) |
| 2 | 固件收 4096B → 原样回显(`net_send` 分块) | PC 收到 4096B 且 `data == test_data` 一致 | 回显错乱 / PC 校验不符 |
| 3 | PC 校验 | 4096B 完全匹配 | 任意字节不符 |
**通过标准**:4096B 完全匹配。任一失败 → 标记 FAIL
**覆盖原则**:DP-04(2KB 接收缓冲边界 + 单次 DMA 上限) |
> **2026-09-23 结案**:TC-NET-604 的"缓冲满回显"路径**判定为非缺陷**——真实生产路径(产品固件 + 真 lftpd FTP 上传,判据 = FTP 协议结果 + **上传后 RETR 回读 SHA-256**)实测 **227 轮全过**(8KB~1MB,含零间隔快速重连与 100 轮浸泡),并发生产 UI 流(`:7003`)600 帧 CRC 全过;原台架"停摆"系**测试程序自吞数据 + 判读工具误判**(详见 `CH395F_Trap_Records.md` Trap 21"教训"节)。
> - **本用例判据(不变)**:PC 发 4096B → 固件分次取空并回显 → **PC 侧内容完全匹配**即 PASS;上传/中止类真实缺陷见 **Trap 23**(回归工具 `MDK-ARM/ftp_abort_probe.py`)。
> **2026-09-23 结案**:TC-NET-604 的"缓冲满回显"路径**判定为非缺陷**——真实生产路径(产品固件 + 真 lftpd FTP 上传,判据 = FTP 协议结果 + **上传后 RETR 回读 SHA-256**)实测 **227 轮全过**(8KB~1MB,含零间隔快速重连与 100 轮浸泡),并发生产 UI 流(`:7003`)600 帧 CRC 全过;原台架"停摆"系**测试程序自吞数据 + 判读工具误判**(详见 `CH395F_Trap_Records.md`「台架纪律」节)。
> - **本用例判据(不变)**:PC 发 4096B → 固件分次取空并回显 → **PC 侧内容完全匹配**即 PASS;上传/中止类真实缺陷见 **TRAP-NET-18**(回归工具 `test/host/ftp_abort_probe.py`)。
> - **台架纪律**:判读一律用 `MDK-ARM/bench_verdict.py`(脚本层 STEP 行为真值,并扣除固件读者吞噬的字节);`probe_classify.py` 的 A/C 判定**不可信**;**禁止**把"固件内非回显读者"(Phase 9/11、固件探针)与固定期望值混用在同一配方里。
> - **Phase 6-only 构建禁止无间隔连跑**:<~1s 重连间隔会触发 Trap 22 失同步(自 run2 起:无 standalone accept、`send -1`、其后全部 connection refused)——快速重连场景须留 ≥15s 间隔;带 Phase 9/11 的构建会被其读者吞数据,不能当对照。
> - **Phase 6-only 构建禁止无间隔连跑**:<~1s 重连间隔会触发 TRAP-NET-17 失同步(自 run2 起:无 standalone accept、`send -1`、其后全部 connection refused)——快速重连场景须留 ≥15s 间隔;带 Phase 9/11 的构建会被其读者吞数据,不能当对照。
### TC-NET-605: 断开重连
@@ -875,9 +875,9 @@ sequenceDiagram
**覆盖原则**:DP-01(多 Socket 并发主路径)、缓冲架构(8×6 块分配,MSS=1024)
> **注**:PC 端 `--max-conn` 须为**并发**计数(连接关闭即减),否则 Phase 7 全量(701+702+704 共 12 连接)会按累计计数超限而拒掉 4~7。该用例已随 Phase 7 实测通过(并发回显落在 sock2/4/5/6/7,见 Trap 18 末)。
> **注**:PC 端 `--max-conn` 须为**并发**计数(连接关闭即减),否则 Phase 7 全量(701+702+704 共 12 连接)会按累计计数超限而拒掉 4~7。该用例已随 Phase 7 实测通过(并发回显落在 sock2/4/5/6/7,见 TRAP-NET-15 末)。
>
> **2026-10-11 复跑(N-31,`FUN_PARA=0x08`)**:**Phase 7 34/34 全通过**,5 路并发 ESTABLISHED + 64 B 回显字节级比对 OK,芯片 Socket 实测落在 **sock2/4/5/6/7**(0/1/3 被 FTP/对时/远显三个监听占用)→ 证明 Socket 可用性不依赖 FUN_PARA。两条**前置排雷**(否则会得到全量假 FAIL):① PC 适配器落在 **Public** 防火墙配置文件会静默丢入站 8081,症状是 TC701/702/704 全部 `CONNECT TIMEOUT`;② 残留的旧 `tcp_server` 进程占着 8081,新服务器绑不上端口不报错。另:`PHASE7_MULTI_COUNT` 须 ≤5(E-23 未修,`NET_MAX_SOCKETS=8` 减去三个常驻监听)。证据链见 `问题记录/CH395F_Trap_Records.md` Trap 18「定案更正」。
> **2026-10-11 复跑(N-31,`FUN_PARA=0x08`)**:**Phase 7 34/34 全通过**,5 路并发 ESTABLISHED + 64 B 回显字节级比对 OK,芯片 Socket 实测落在 **sock2/4/5/6/7**(0/1/3 被 FTP/对时/远显三个监听占用)→ 证明 Socket 可用性不依赖 FUN_PARA。两条**前置排雷**(否则会得到全量假 FAIL):① PC 适配器落在 **Public** 防火墙配置文件会静默丢入站 8081,症状是 TC701/702/704 全部 `CONNECT TIMEOUT`;② 残留的旧 `tcp_server` 进程占着 8081,新服务器绑不上端口不报错。另:`PHASE7_MULTI_COUNT` 须 ≤5(E-23 未修,`NET_MAX_SOCKETS=8` 减去三个常驻监听)。证据链见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15。
---
@@ -913,7 +913,7 @@ sequenceDiagram
| **类型** | 功能测试(文件发送) |
| **标题** | MCU 流式发送 100KB 至 PC 并落盘校验 |
| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(先连接等待数据)<br>2. `ENABLE_PHASE8_TESTS`,`PHASE8_FILE_SIZE=102400`,`PHASE8_DIRECTION=SEND`<br>3. `net_init` 完成,MSS=1024 |
| **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)<br>2. 循环填充一个滑动窗口(≤ s_tx_buf)并调用 `net_send`;`net_send` 内部按硬件发送 FIFO(`NET_SEND_CHUNK_MAX`=1KB,见 Trap 20)自动拆分写入,直至 100KB 发完<br>3. PC 接收全部字节写入 `rx_100k.bin`,按递增模式或预置 CRC32 校验 |
| **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)<br>2. 循环填充一个滑动窗口(≤ s_tx_buf)并调用 `net_send`;`net_send` 内部按硬件发送 FIFO(`NET_SEND_CHUNK_MAX`=1KB,见 TRAP-NET-16)自动拆分写入,直至 100KB 发完<br>3. PC 接收全部字节写入 `rx_100k.bin`,按递增模式或预置 CRC32 校验 |
| **通过标准** | PC 收满 102400B 且逐字节/CRC32 校验一致,MCU 无发送错误 |
| **覆盖原则** | DP-04(分块发送、DMA 边界)、缓冲架构(1KB 发送缓冲) |
| **实测** | ✅ PASS(2026-08-26):固件 `PHASE8_DIRECTION=SEND`,PC `tcp_file --op recv` 收满 102400B,递增模式逐字节校验通过,CRC32=0x9A0E0C8C,耗时 1209ms / 82.7 KB/s,落盘 `rx_100k.bin` 成功。 |
@@ -927,9 +927,9 @@ sequenceDiagram
| **类型** | 功能测试(文件接收) |
| **标题** | PC 流式发送 100KB 文件,MCU 接收并校验 |
| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op send --file tx_100k.bin --size 102400`(先连接并发送;无文件则生成 100KB 递增模式)<br>2. 同 TC-NET-801(`PHASE8_DIRECTION=RECV`) |
| **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)<br>2. 循环 `net_recv`(≤2KB/次,Trap 13 一次读空)→ 按 offset 字节校验 + 累计 CRC32,直至收满 100KB<br>3. PC 在文件末尾附 4 字节 CRC32(小端);MCU 收齐后比对 CRC32 |
| **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)<br>2. 循环 `net_recv`(≤2KB/次,保守一次读空)→ 按 offset 字节校验 + 累计 CRC32,直至收满 100KB<br>3. PC 在文件末尾附 4 字节 CRC32(小端);MCU 收齐后比对 CRC32 |
| **通过标准** | MCU 收满 102400B,字节校验全过且 CRC32 匹配,无溢出/截断 |
| **覆盖原则** | DP-04(接收缓冲 2KB 一次读空)、Trap 13 |
| **覆盖原则** | DP-04(接收缓冲 2KB 一次读空) |
| **实测** | 🟡 待验证(参考:默认递增模式 100KB 的 CRC32 经 SEND 实测为 `0x9A0E0C8C`,RECV 方向 MCU 累计 CRC32 应与之相等) |
### TC-NET-803: 100KB 回显 round-trip(可选)
@@ -955,7 +955,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON
```
- **发送**:`phase8_fill(buf, chunk, offset)` 按窗口生成递增模式;`net_send` 经消息队列转发 netTask,由 `net_send_locked` 按发送缓冲(1KB)/`CH395F_SPI_DMA_MAX_PAYLOAD` 自动拆分。
- **接收**:`net_recv` 每次读 ≤2KB(=接收缓冲,Trap 13 一次读空),`phase8_verify(buf, n, offset)` 字节校验 + 累计 CRC32。
- **接收**:`net_recv` 每次读 ≤2KB(=接收缓冲,保守一次读空),`phase8_verify(buf, n, offset)` 字节校验 + 累计 CRC32。
- **100KB 不整体驻留 RAM**:`s_tx_buf`/`s_rx_buf`=32KB 仅作滑动窗口;`PHASE8_FILE_SIZE` 控制总量。
- **PC `tcp_file` 子命令**(新增,作 Client 连 MCU:8080):`recv` 接收 `--size` 字节落盘并校验;`send` 读 `--file` 发送并附 4 字节 CRC32 尾。
@@ -1117,8 +1117,8 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON
**二段关闭语义**(本 Phase 首跑实证,详见实验方案 E2 补充发现):LISTEN 态
`CLOSE_SOCKET` 返 SUCCESS 但只拆监听(TCP 01→00),byte0 恒 05;需再发一次
CLOSE 才落 00。与 Trap 07 的 `0x05/0x00` 驻留签名同源;`sock_reset_poll` 的
WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 Trap 09 open 重试吸收。
CLOSE 才落 00。与 TRAP-NET-07 的 `0x05/0x00` 驻留签名同源;`sock_reset_poll` 的
WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 TRAP-NET-09 open 重试吸收。
**运行方法**:`ch395f_test.h` 启用 `ENABLE_PHASE15_TESTS`(自动进入硬件层模式,
勿与 NET 层 Phase 混选)→ `@build` → 烧录 → 串口看 `=== Phase 15: 20/20 PASSED ===`。
@@ -1152,18 +1152,17 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠
| ID | 陷阱 | 现象 | 修复 |
|----|------|------|------|
| TRAP-03 | DHCP xid 偏移错误 | DHCP 握手失败 | xid 在 `data[4:8]` |
| TRAP-04 | UDP 无 SENDBUF_FREE 等待 | 后续接收挂起 | 每次 write 后等 SENDBUF_FREE |
| TRAP-05 | PHY 不兼容 RTL8305NBI | 链路抖动 | `ch395f_set_phy(0x08)` 强制 100M_FULL |
| TRAP-06 | KeepAlive 非 500ms 倍数 | 参数不生效 | 必须 500ms 倍数 |
| TRAP-07 | 只 `disconnect` 不 `close`(bit3=1) | 停在 `0x05/0x00`、重开返 `0x20` | 直接 `close_socket`(§5.35 自动断链,**非 RST**);见 Trap 07「定案更正」与 TC-NET-204 |
| TRAP-08 | RECV 电平中断无限循环 | netTask 饿死 | `net_poll` 每次只一批,数据在应用中读 |
| TRAP-09 | PHY 刚建立 link 立即 open | 首次连接失败 | 启动延时 + 重试 |
| TRAP-10 | 消息队列值拷贝丢失 | API 返回旧值 | 改用 `xTaskNotify` |
| TRAP-11 | `net_accept_locked` 拒 ESTABLISHED | FTP PASV 失败 | 允许 ESTABLISHED |
| TRAP-12 | FatFS 不接受 `/` 前缀 | FTP 路径解析失败 | `to_fatfs_path()` 剥离 |
| **TRAP-13** | **`recv_len` 分批读取归零** | **大包截断** | **DMA 缓冲增至 4100,一次读空 ✅ 已修复** |
| **TRAP-14** | **`NET_SEND_BUF_SIZE` > DMA 限制** | **>1496B 发送静默失败** | **`net_send_locked` 按 `NET_DMA_MAX_PAYLOAD` 分块 ✅ 已修复** |
| TRAP-NET-03 | DHCP xid 偏移错误 | DHCP 握手失败 | xid 在 `data[4:8]` |
| TRAP-NET-04 | UDP 无 SENDBUF_FREE 等待 | 后续接收挂起 | 每次 write 后等 SENDBUF_FREE |
| TRAP-NET-05 | PHY 不兼容 RTL8305NBI | 链路抖动 | `ch395f_set_phy(0x08)` 强制 100M_FULL |
| TRAP-NET-06 | KeepAlive 非 500ms 倍数 | 参数不生效 | 必须 500ms 倍数 |
| TRAP-NET-07 | 只 `disconnect` 不 `close`(bit3=1) | 停在 `0x05/0x00`、重开返 `0x20` | 直接 `close_socket`(§5.35 自动断链,**非 RST**);见 TRAP-NET-07与 TC-NET-204 |
| TRAP-NET-08 | RECV 电平中断无限循环 | netTask 饿死 | `net_poll` 每次只一批,数据在应用中读 |
| TRAP-NET-09 | PHY 刚建立 link 立即 open | 首次连接失败 | 启动延时 + 重试 |
| TRAP-NET-10 | 消息队列值拷贝丢失 | API 返回旧值 | 改用 `xTaskNotify` |
| TRAP-NET-11 | `net_accept_locked` 拒 ESTABLISHED | FTP PASV 失败 | 允许 ESTABLISHED |
| 旧机理(未证实) | `recv_len` 分批读取归零 | 227 轮往返未复现 | 保守按一次读空执行;定向 TC 待补 |
| **TRAP-NET-12** | **`NET_SEND_BUF_SIZE` > DMA 限制** | **>1496B 发送静默失败** | **`net_send_locked` 按 `NET_DMA_MAX_PAYLOAD` 分块 ✅ 已修复** |
---
+4 -4
View File
@@ -120,13 +120,13 @@
### 8.1 连接被拒 / 长时间无欢迎
- 确认 PC 与固件同网段、IP 正确;上电后等待约 7s(PHY 稳定 + `ftpTask` 延迟)再连。
- 若反复 `open_socket` 失败:见 **TRAP-09**(PHY 未稳定;固件已做 3 次重试 + 7s 延迟,仍失败检查网线/交换机)。
- 若反复 `open_socket` 失败:见 **TRAP-NET-09**(PHY 未稳定;固件已做 3 次重试 + 7s 延迟,仍失败检查网线/交换机)。
### 8.2 PASV 失败 / 数据通道连不上
- 确认 PC 防火墙未阻断高端口(PASV 动态端口)。
- **数据通道必须先 `connect` 再发命令**(如 `LIST`/`STOR`),顺序反了会失败(脚本已遵循)。
- **TRAP-11**:`net_accept_locked` 在 `ESTABLISHED` 状态导致 PASV 失败——需校验 `ESTABLISHED`。
- **TRAP-NET-11**:`net_accept_locked` 在 `ESTABLISHED` 状态导致 PASV 失败——需校验 `ESTABLISHED`。
### 8.3 STOR 报 FR_INVALID_NAME (err=6)
@@ -134,13 +134,13 @@
### 8.4 大文件传输中途 RST / 速度抖动
- **TRAP-13** / 设计约束:CH395F `WRITE_SEND_BUF` 芯片侧吞吐限制,单帧 >1KB 才稳定;TCP 背压会"堵一会儿",属正常流控,不丢数据。
- **设计约束**:CH395F `WRITE_SEND_BUF` 芯片侧吞吐限制,单帧 >1KB 才稳定;TCP 背压会"堵一会儿",属正常流控,不丢数据。
- 接收侧 `recv_len` 首包可能较大、次包可能为 0,**必须循环读直到连接关闭**(脚本已处理)。
- 若持续 RST:检查 **SPI 分频 = 8**(PCB 设计限制)、`net_poll()` 周期(10ms)是否正常、中断是否统一在 `net_poll()` 中经 `GET_GLOB_INT_STATUS` 处理。
### 8.5 路径问题
- **TRAP-12**:FatFS 根路径 `/` 前缀由 `to_fatfs_path()` 统一处理;客户端用绝对路径(如 `/UPLOAD.DAT`)即可。
- **FatFS 路径口径**:根路径 `/` 前缀由 `to_fatfs_path()` 统一处理(详见 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1);客户端用绝对路径(如 `/UPLOAD.DAT`)即可。
### 8.6 FTP 从不监听(21 端口一直拒绝)
+2 -2
View File
@@ -4,7 +4,7 @@
> 全部公开/私有函数,并向上延伸到 FTL(`nand_ftl.c`)与 FatFS diskio。
> 详细芯片行为见 `docs/芯片手册/GD5F2GQ5UExxG.md`,已知陷阱见 `docs/问题记录/GD5F2GQ5UE_Trap_Records.md`。
>
> **2026-10-03 错误通道硬化**(E-10/E-42/E-53/E-54/E-56/E-57,见 `问题记录/GD5F2GQ5UE_Trap_Records.md` Trap 06):
> **2026-10-03 错误通道硬化**(E-10/E-42/E-53/E-54/E-56/E-57,见 `问题记录/GD5F2GQ5UE_Trap_Records.md` TRAP-GD5F-06):
> `wait_busy` 区分"超时"(`GD5F_BUSY_TIMEOUT`)与"真故障"(`GD5F_ERROR`)、`bbt_scan` 失败**放弃持久化**、
> init 全链路检查返回值 + **回读 B0h 校验 ECC 使能**、`read` 的 raw 契约文档化、公开 API 统一 `int32_t`。
> 套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`。
@@ -413,7 +413,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| **前置条件** | SPI 已初始化 |
| **测试步骤** | `gd5f_block_erase(block)`(注意传**块编号**,内部转块首页地址,见陷阱 01) |
| **通过标准** | 返回 `GD5F_OK`,无 `E_FAIL` |
| **覆盖原则** | `gd5f_block_erase`(Trap 01 修复点) |
| **覆盖原则** | `gd5f_block_erase`(TRAP-GD5F-01 修复点) |
### TC-GD5F-604: write_enable / read_status(WEL 位)
@@ -318,9 +318,9 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时
## 6. 实测基线(2026-09-22 台架,已重锁)
> 2026-09-22 注记:该轮代码变更(缓冲块布局与 MSS 解耦 + 超限读 OVERSIZE 金丝雀,见 Trap 21)
> 2026-09-22 注记:该轮代码变更(缓冲块布局与 MSS 解耦 + 超限读 OVERSIZE 金丝雀)
> 不改变任何芯片行为参数(MSS=1024、块布局 4/2、DMA 缓冲 2052B 均不变)→ 本节全部基线继续有效,无需重锁。
> (2026-09-23 补:Trap 21 的"RX 冻结"结论已结案为非缺陷,该轮代码变更按防御性/可观测性改进保留。)
> (2026-09-23 补:TC-NET-604 的"RX 冻结"结论已结案为非缺陷,该轮代码变更按防御性/可观测性改进保留。)
### 6.1 M1 段(Phase 12,SPI 层)
@@ -391,7 +391,7 @@ per-recv 日志移除的调度效应,非芯片行为变化:`queue_rt_recv`
(旧值对照:`prod_cycle_mcu` 7142µs → 6707µs = P0-b 移除请求路径 per-recv 日志的 ~435µs;
`ui_loop`/`relisten` 两档同值 = 与日志无关的段。)
`relisten` 阈值说明:修复前 588000µs(Trap 09 `osDelay(500)` 忙等)→ crit 500ms 即 D10 指纹;
`relisten` 阈值说明:修复前 588000µs(TRAP-NET-09 `osDelay(500)` 忙等)→ crit 500ms 即 D10 指纹;
D10 修复后实测 89ms(= 30ms 重试间隔 + 约 60ms 芯片沉降),故 warn 放宽到 **100ms**、crit 保持 500ms
(仍能捕获"退回阻塞重试"的回归)。
@@ -589,7 +589,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
fun_para bit3=1 保留缓冲);
3. **现有甄别为何拦不住**:延迟 DISCONNECT 时刻 `ch395f_get_socket_status` 回读 `tcp=0x00`,不反映新连接
的 ESTABLISHED 态 → "回读芯片状态判陈旧"(`st[1]==ESTABLISHED` 才丢弃)在此场景恒不成立;
4. 与 D10(Trap 09 沉降)、D14(静默回收)同属"单连接 Socket 快速复用"家族。
4. 与 D10(TRAP-NET-09 沉降)、D14(静默回收)同属"单连接 Socket 快速复用"家族。
**已试并回退的修法(保留结论,勿重复投入)**:
① 传输结束不关数据连接(保留监听器复用)→ **破坏 FTP EOF 语义**(客户端等不到 EOF 整会话挂死),必须关闭;
@@ -701,7 +701,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
5. **S2** 差量/RLE + 帧版本号 + 由拉改推(**根治,收益最大**:典型变化 <100B → 单段一帧,延迟 ≈ 一个 RTT;
稳态带宽 32KB/s → ≈0;D1 影响面 4 段 → 1 段;新命令走同一帧格式与 CRC,旧客户端兼容);
6. **S4** 发送块配额与 MSS:48 块按实际使用的 3 个 socket 重新分配(各 16 块),MSS → 1460
(3209B 从 4 段降到 1~2 段);**约束**:单次写长度必须 ≤ 发送缓冲(Trap 20 铁律),
(3209B 从 4 段降到 1~2 段);**约束**:单次写长度必须 ≤ 发送缓冲(TRAP-NET-16 铁律),
改分配策略需同步 `net_bind` 重复分配路径,改前先读 `CH395F_Trap_Records.md`,改后跑 Phase 8 吞吐用例;
**另**:MSS/块配额变更**必须**跑可信用例(`test/host/ch395f_socket_test.py tcp_client` 全序列 + 新连接独立 4096B 对照),判据 = **客户端侧内容完全匹配**(勿用带固件内读者的台架配方,其短收是读者吞数据不是故障);
7. **S5** 去掉一次 3200B 拷贝(`g_tx` 直接承接位图,省 ~3.2KB 常驻 RAM;改前跑 `scripts/ram_report.py`);
@@ -765,7 +765,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
| RemoDispBus 协议与接收设计(含 INIT 8B 勘误) | `docs/UI设计/RemoDispBus协议与接收设计说明.md` |
| UI 设计架构(离屏渲染 + 推流) | `docs/UI设计/UI_设计架构.md` |
| BSD Socket API 与双端后端 | `docs/网络/BSD_Socket_API_使用指南.md` |
| CH395F 陷阱史(Trap 9/20 等) | `docs/问题记录/CH395F_Trap_Records.md` |
| CH395F 陷阱史(TRAP-NET-09/-16 等) | `docs/问题记录/CH395F_Trap_Records.md` |
| 错误码传播审计(E-02 / E-04 / E-33 / E-34) | `docs/问题记录/错误码传播审计.md` |
| CH395F 测试套件(吞吐 Phase 8 等) | `docs/测试说明/CH395F_Test_Guide.md` |
| RAM 预算(`ui_net_sink` 6.4KB 常驻) | `docs/内存管理/RAM使用分析与优化方案.md` |
+1 -1
View File
@@ -107,7 +107,7 @@
| `DR-<nnn>` | 决策记录 | `docs/子系统设计/网络Socket监听生命周期设计说明.md` 的 `D1~D10` | `[计划] docs/架构设计/决策记录登记簿.md` | `DR-001` |
| `OQ-<nnn>` | 待确认 / 开放问题 | `B0-a/b/c`、`待确认-N` | `docs/任务规划/基线与待确认.md` | `OQ-001` |
| `RSK-<nn>` | 风险(**文档内编号**) | 保护规划 `R1~R8`、看门狗 `R1~R7` | 各文档风险表(引用必须带文档名) | 各文档自 `RSK-01` |
| `TRAP-<MOD>-<nn>` | 陷阱记录 | `Trap 07`、`TRAP-07`、`陷阱 1` | `docs/问题记录/<模块>_Trap_Records.md` | 保留原号(`TRAP-NET-01~25`、`TRAP-GD5F-01~06`、`TRAP-RTC-01~`) |
| `TRAP-<MOD>-<nn>` | 陷阱记录 | `Trap 07`、`TRAP-07`、`陷阱 1` | `docs/问题记录/<模块>_Trap_Records.md` | 保留原号(`TRAP-NET-01~21`——NET 域 2026-10-13 全库迁移并重排连续,沿革见该文档、`TRAP-GD5F-01~06`、`TRAP-RTC-01~`) |
| `GAP-<nn>` | 模拟器差距 | `H-1~H-7`、`L-1~L-4`、差距清单 `N-1~N-6` | `docs/测试说明/模拟器差距清单.md` | `GAP-01` |
| `SC-<nn>` | 实机验证场景 | 实机预检 `A1~A8`、看门狗验证用例 `V2/V4/V5` | `docs/测试说明/实机测试实施指南.md` | `SC-01` |
| `T0/T0.5/T1/T2/T3` | **测试分层**(原 `L0/L0.5/L1/L2/L3`) | 测试分层五档 | `docs/测试说明/测试开发标准流程.md` §1 | 固定五档 |
+24 -24
View File
@@ -1,8 +1,8 @@
# BSD Socket API 使用指南
> **恢复说明**:本文档曾在提交 `4e9ac0d`(2026-08-28)中误删,2026-09-13 从 git 历史恢复。API 随代码多轮同步(2026-09-13:`net_init` 4 参数 / `net_socket` 2 参数 / 网络代次号 / `NET_NO_OS` 双端构建;2026-09-23:`net_recv_to` / `net_sock_state` / `net_resync_listen` / `net_relisten`、L3 pending 化、D10 非阻塞重监听、Trap 22 周期对账、Trap 23 排渣隔离)。详见文末变更记录。
> **恢复说明**:本文档曾在提交 `4e9ac0d`(2026-08-28)中误删,2026-09-13 从 git 历史恢复。API 随代码多轮同步(2026-09-13:`net_init` 4 参数 / `net_socket` 2 参数 / 网络代次号 / `NET_NO_OS` 双端构建;2026-09-23:`net_recv_to` / `net_sock_state` / `net_resync_listen` / `net_relisten`、L3 pending 化、D10 非阻塞重监听、TRAP-NET-17 周期对账、TRAP-NET-18 排渣隔离)。详见文末变更记录。
>
> **伴读文档**:本文回答"上层该怎么调";**内部函数之间的调用关系、每个函数的设计作用与意图**(netTask / 消息队列 / L3 挂起 / D10 重试 / Trap 22 对账 / Trap 23 排渣各子系统,含总体调用图与端到端时序)见 `docs/网络/BSD_Socket_API_函数调用关系.md`。
> **伴读文档**:本文回答"上层该怎么调";**内部函数之间的调用关系、每个函数的设计作用与意图**(netTask / 消息队列 / L3 挂起 / D10 重试 / TRAP-NET-17 对账 / TRAP-NET-18 排渣各子系统,含总体调用图与端到端时序)见 `docs/网络/BSD_Socket_API_函数调用关系.md`。
## 目录
@@ -62,7 +62,7 @@
| `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 22 周期对账 + 监听重建**合一** reconciler(`net_reconcile_tick`,100ms,分级去抖+冷却,形态①②③④⑤/U11,N34 阶段 3) |
| `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_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现(仅 MCU 端编译) |
@@ -158,7 +158,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_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 22 对账+监听重建合一 reconciler 100ms),再处理队列 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **3 个非阻塞 tick**(D10 重监听重试 → L3 挂起 recv 完成 → TRAP-NET-17 对账+监听重建合一 reconciler 100ms),再处理队列 |
### 3.4 内部 API 说明
@@ -181,22 +181,22 @@ int net_close_sock(net_sock_t *p_sock);
- **队列阻塞语义**:只有 `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 09 / 22 / 23)
### 3.6 芯片/本层状态对账与重监听自愈(TRAP-NET-09/-17/-18)
单连接模型下"监听 Socket = 数据通道",多种芯片行为会造成**芯片侧与本层状态失同步**,纯事件(中断)驱动无法自愈。当前版本有**四层自愈**(N34 阶段 3:原 200ms 周期对账与 100ms 监听器 pump 合并为**单一 100ms `net_reconcile_tick`**),全在 netTask 内、全非阻塞、不冻结主循环:
| 机制 | 触发 | 动作 | 关键参数 |
|---|---|---|---|
| **`auto_relisten`** | 数据通道正常 EOF / DISCONNECT | 重开 + 重新 LISTEN,回 LISTENING;应用层无需 close/重 listen | `net_listen()` 置位;`net_close()` 清 |
| **D10 非阻塞重监听重试** | 重开失败(Trap 09:芯片 close 后需沉降) | 单次尝试失败即登记待重试,每轮到点重试;超时限放弃并记 `relisten give up` | 重试间隔 30ms / 时限 2s(自首次尝试);同 fd 有在途挂起 recv 时跳过(L3 保险,防毁在途连接) |
| **Trap 22 周期对账 + 监听重建(合一 `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 23 排渣隔离** | 异常释放(芯片 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 时限**,否则监听被放弃) |
| **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-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 时限**,否则监听被放弃) |
**行为结论**:监听器恢复从"一次性事件"变为"登记 → 到点重试"过程:
- 正常断连:通常一次重开即成;失败时 ~30ms 量级恢复,最迟 ~1s(2s 后放弃,错误日志可见)。恢复窗内该端口拒绝新连接 → 调用方重连逻辑须容忍 ~1s 中断。
- **快速复连不再"监听死亡"(Trap 22)**:对账保证失同步按分级去抖在 100~400ms 内修正(②1 轮 / ③④⑤2 轮 / ①4 轮,100ms/轮;2026-09-23 生产路径:待机后 UI `:7003` 快速复连 10/10;FTP `:21` 100ms/0ms/600ms 间隔各 50/50;零重建风暴)。
- **中止复连首上传不再被静默截断(Trap 23)**:RST/FIN 中止后首笔复连上传完整通过(RST 10/10、FIN 5/5,SHA-256 全匹配)。详见 `docs/问题记录/CH395F_Trap_Records.md` Trap 22/23。
- **快速复连不再"监听死亡"(TRAP-NET-17)**:对账保证失同步按分级去抖在 100~400ms 内修正(②1 轮 / ③④⑤2 轮 / ①4 轮,100ms/轮;2026-09-23 生产路径:待机后 UI `:7003` 快速复连 10/10;FTP `:21` 100ms/0ms/600ms 间隔各 50/50;零重建风暴)。
- **中止复连首上传不再被静默截断(TRAP-NET-18)**:RST/FIN 中止后首笔复连上传完整通过(RST 10/10、FIN 5/5,SHA-256 全匹配)。详见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-17/-18。
---
@@ -247,7 +247,7 @@ int main(void)
- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT)
- 触发事件回调
- 处理数据接收
- `net_process_messages()` 每轮先跑 3 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 唤醒,E-02 修复)→ Trap 22 芯片/本层对账 + 监听重建合一 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
@@ -1012,12 +1012,12 @@ int net_drop_conn(int sockfd);
应用**放弃会话**(对时 30s 预算耗尽、协议超时)时主动拆掉当前 TCP 会话并**保留监听**——单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(PC 内核自动 ACK keepalive,芯片僵尸判不死,定案见 `docs/网络/N34-自愈审计/实验方案.md` E13),而应用又不得 `net_close` 监听 fd(毁监听);本 API 补上"拆连接不拆监听"的应用侧形态。
- **netTask 侧**:先了结本 fd 挂起 recv(排空/取消 + 必通知),再 `sock_reset` QUARANTINE(FIN + 排渣 + 排空事件 + 等 byte0=00H,Trap 23 异常释放语义,400ms 底);本层收口 CLOSED;`auto_relisten` fd 随即 `net_listener_try` 即时重监听(失败交 D10 重试 + reconciler 形态④)
- **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,不动监听
- **EOF 路径不要调**:EOF 时 net 层 `auto_relisten` 已接管(§12.4),无需也不应再拆
- 经消息队列委托 netTask(同其它 `net_*` API,非 netTask 上下文调用);等待期 netTask 有界占用 ~400-600ms
- 模拟器(`net_socket_sim.c`,数据 fd ≠ 监听 fd)实现为关数据连接、监听常驻
- 台架定案(2026-10-13,E-02):CH395F close **无 RST 语义**——对端内核存活但不 close 时芯片停 FIN_WAIT_2 长尾,监听恢复受限于对端 close;对端配合则 TIME_WAIT 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。不配合场景兜底 = 芯片 TIMEOUT/keepalive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` Trap 07
- 台架定案(2026-10-13,E-02):CH395F close **无 RST 语义**——对端内核存活但不 close 时芯片停 FIN_WAIT_2 长尾,监听恢复受限于对端 close;对端配合则 TIME_WAIT 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。不配合场景兜底 = 芯片 TIMEOUT/keepalive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-07
#### 自愈与查询 API
@@ -1058,7 +1058,7 @@ void net_process_messages(void);
1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6)
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ tripwire:挂起超 60s 只 `[ERR]` 留痕一次,不强制了结(N34 阶段 1.5)
3. **Trap 22 芯片/本层对账 + 监听重建(合一 `net_reconcile_tick`)**:100ms 周期,判据读驱动状态缓存(`ch395f_sock_state_cached`),修正**五类**失同步(①②③④⑤)+ U11 被动重同步——原 200ms 对账与 100ms 监听器 pump 已合并为单一 tick,**唯一重建入口**=形态③④(§3.6)
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)。
@@ -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 22 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,丢中断/静默回收/监听失位自愈,§3.6)。
- `net_process_messages()`:除串行处理队列请求外,每轮先跑 3 个**非阻塞**检查——D10 重监听重试(断连后监听器恢复)→ L3 挂起 recv 完成检查(阻塞 recv 的唤醒)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,丢中断/静默回收/监听失位自愈,§3.6)。
建议主循环周期 ≤ 10ms(本项目 1ms)。若 netTask 主循环停摆(断点、任务被挂起),监听器不会恢复、阻塞 recv 不会完成——这是软件侧"监听死机"的最常见原因。
@@ -1291,7 +1291,7 @@ static int read_time_frame(int conn_sock, uint8_t buf[FRAME_LEN]) {
- 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`(默认 4096 字节)
- TCP 需要等待发送缓冲区空闲(`send_ready` 标志)
- UDP 单包载荷最大 1472 字节(写入超过此值,CH395F 会自动分片为多个 UDP 包发送)
- 接收侧:每 Socket RX 缓冲 2KB(4×512B,即芯片接收窗口)、TX FIFO 1KB(2×512B)、MSS=1024。对端常态把 2KB 窗口填满是**正常**现象,不等于"冻结"(Trap 21 结案,见 §12.10)
- 接收侧:每 Socket RX 缓冲 2KB(4×512B,即芯片接收窗口)、TX FIFO 1KB(2×512B)、MSS=1024。对端常态把 2KB 窗口填满是**正常**现象,不等于"冻结"(TC-NET-604 结案,见 §12.10)
### 12.4 如何处理连接断开?
@@ -1317,7 +1317,7 @@ if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
**⚠ 单连接模型(MCU 端 CH395F)的断开处理**:监听 Socket 在 CONNECT 后**自身即数据通道**
(`g_client == g_listen`);`net_recv()` 返回 0(EOF)时 net 层已自动把该 Socket 放回 LISTEN
(`auto_relisten`,BSD 风格"监听常驻")。此时**不得**再对它调 `net_close()`——那会把监听一起关掉,
后续连接直接被拒(连接与监听同一 fd,重建还受 Trap 09 时序限制)。正确写法:
后续连接直接被拒(连接与监听同一 fd,重建还受 TRAP-NET-09 时序限制)。正确写法:
```c
} else if (len == 0) {
@@ -1440,17 +1440,17 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
需要回传请用调用方提供的指针,或复用通知值。(2026-09-21 实证:曾按"加 `msg.err` 字段"方式修错误码,
结果 `ui_net_sink` 每次空轮询都拿到栈垃圾 → 误判为"真错误" → 连上即 `net_close`。)
### 12.10 监听器自愈行为与沉降/隔离(D10 / Trap 09、Trap 22、Trap 23 / N-32)
### 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()` 主循环在跑。
| 场景 | 修复前症状 | 机制 | 生产验证(2026-09-23) |
|---|---|---|---|
| **Trap 22** 快速复连丢中断 → 监听静默死亡 | 待机后 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 23** 中止后首笔复连上传被静默截断 ≤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 全匹配 |
| **Trap 21**(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 |
| **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-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 |
**排障判据**(无 Wireshark 环境,纯生产路径):`MDK-ARM/ftp_freeze_probe.py`(真实 STOR + RETR SHA-256)、`MDK-ARM/ftp_abort_probe.py`(RST/FIN 中止复连)、`MDK-ARM/tcp_reconnect_probe.py`(快速复连存活甄别)、`MDK-ARM/bench_verdict.py`(脚本层 STEP 为真值;**`probe_classify.py` A/C 判读不可信**,见 retracted 归档 README)。详见 `docs/问题记录/CH395F_Trap_Records.md`。
**排障判据**(无 Wireshark 环境,纯生产路径):`MDK-ARM/ftp_freeze_probe.py`(真实 STOR + RETR SHA-256)、`test/host/ftp_abort_probe.py`(RST/FIN 中止复连)、`MDK-ARM/tcp_reconnect_probe.py`(快速复连存活甄别)、`MDK-ARM/bench_verdict.py`(脚本层 STEP 为真值;**`probe_classify.py` A/C 判读不可信**,见 retracted 归档 README)。详见 `docs/问题记录/CH395F_Trap_Records.md`。
---
@@ -1460,10 +1460,10 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
|------|------|
| 2026-10-13 | **N34 阶段 3 自愈合并 + E-02 收口**(实施规格 `docs/网络/N34-自愈审计/实施方案.md` §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` E-02 收口台架) |
| 2026-10-13 | **N34 阶段 1.5 recv 语义主流化**(规格 `docs/网络/N34-自愈审计/实施方案.md` §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-自愈审计/README.md`):删除手动自愈 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 09 启动形态) |
| 2026-10-13 | **N34 阶段1 API 面收敛**(审计见 `docs/网络/N34-自愈审计/README.md`):删除手动自愈 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 22 对账、Trap 23 排渣隔离后的现状:新增 `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(自愈行为数据与排障判据,含 Trap 21 结案) |
| 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 结案) |
| 2026-09-21 | **修正公开 API 的错误码回传**:`net_msg_t` 按值拷入队列 → netTask 对消息的写入调用方不可见,改用 `xTaskNotify()` 通知值回传具体错误码(负值),公开 API 恢复 errno 后统一返回 -1(此前被覆盖成 `NET_ERR`,使 `net_get_errno() != NET_ERR_WOULDBLOCK` 的"空轮询"判断恒成立 → `ui_net_sink` 连上即 `net_close`);§12.4 补单连接模型断开处理(EOF 时不得 `net_close` 监听 socket);§12.9 补错误码回传约束 |
| 2026-09-15 | 同步配置引擎属主变更(P1-3):`net_init` 调用方说明改为"netTask 只读 RAM 副本"(网口配置由 defaultTask 在就绪屏障前加载);模拟器端启动序列补 `net_cfg_save_run()`(主循环 = 模拟器侧配置引擎属主,执行 UI 保存请求) |
| 2026-09-13 | 从 git 历史恢复(`4e9ac0d` 2026-08-28 误删);同步当前代码:`net_init` 4 参数(新增 `mac`)、`net_socket` 2 参数(移除 `protocol`)、新增 `net_get_generation` / `net_generation_inc` / `net_listen_locked`、`NET_NO_OS` 双端构建、netTask 周期 10ms → 1ms |
@@ -14,7 +14,7 @@
## 1. 一句话架构
**一切芯片(CH395F)操作收敛到唯一执行者 netTask 串行完成**;其他任务通过"入队消息 + 任务通知"表达请求,结果只经**任务通知值**回传;阻塞等待**不独占 netTask**(挂起登记 + 主循环节拍完成);监听器**常驻**(`auto_relisten` + D10 非阻塞重试 + **Trap 22 周期对账 + 监听重建合一 `net_reconcile_tick`(唯一重建入口=形态③④,100ms)** + Trap 23 排渣隔离,四层自愈;N34 阶段 3 起原 N-32 监听器 pump 已并入对账)。
**一切芯片(CH395F)操作收敛到唯一执行者 netTask 串行完成**;其他任务通过"入队消息 + 任务通知"表达请求,结果只经**任务通知值**回传;阻塞等待**不独占 netTask**(挂起登记 + 主循环节拍完成);监听器**常驻**(`auto_relisten` + D10 非阻塞重试 + **TRAP-NET-17 周期对账 + 监听重建合一 `net_reconcile_tick`(唯一重建入口=形态③④,100ms)** + TRAP-NET-18 排渣隔离,四层自愈;N34 阶段 3 起原 N-32 监听器 pump 已并入对账)。
---
@@ -22,11 +22,11 @@
| 执行域 | 运行位置 / 节奏 | 允许的操作 | 典型调用者 |
|---|---|---|---|
| **netTask 主循环** | 独立 FreeRTOS 任务,1ms/tick | **唯一**可碰芯片(SPI2)的地方;3 个非阻塞 tick(D10 重试 / L3 挂起 / Trap 22 对账+监听重建合一 reconciler);队列分派;事件处理器;用户回调 | `net_poll` / `net_process_messages` 及其下游全部 `_locked` / `_tick` / `handle_*` |
| **netTask 主循环** | 独立 FreeRTOS 任务,1ms/tick | **唯一**可碰芯片(SPI2)的地方;3 个非阻塞 tick(D10 重试 / L3 挂起 / TRAP-NET-17 对账+监听重建合一 reconciler);队列分派;事件处理器;用户回调 | `net_poll` / `net_process_messages` 及其下游全部 `_locked` / `_tick` / `handle_*` |
| **应用任务侧** | ftpTask / uiTask / timeSyncTask / testTask 等 | 经队列的公共 API(入队 + `xTaskNotifyWait` 等回执);纯 RAM 直调 API;**禁止直碰芯片** | 各服务任务 |
| **直调快路径** | 同步调用,不经过队列 | `net_socket` / 查询族 / 回调注册(纯 RAM);**直碰 SPI 的三例外**:`net_bind`(UDP) / `net_sendto` / `net_recvfrom` | 初始化阶段、netTask 上下文、测试套件(硬件层测试模式下 netTask 自挂起,testTask 独占芯片) |
> ⚠ 直碰 SPI 的三例外**只能在"芯片访问者唯一"的上下文**调用:产品构建中应用任务一律走队列(TCP 全路径);三例外的实际消费者是测试套件(见 AGENTS.md"测试构建")。从其他任务直接调用会与 `net_poll` 的 SPI 事务竞争,破坏 CH395F 寄存器(Trap 16 实证:CONNECT 超时)。
> ⚠ 直碰 SPI 的三例外**只能在"芯片访问者唯一"的上下文**调用:产品构建中应用任务一律走队列(TCP 全路径);三例外的实际消费者是测试套件(见 AGENTS.md"测试构建")。从其他任务直接调用会与 `net_poll` 的 SPI 事务竞争,破坏 CH395F 寄存器(TRAP-NET-14 实证:CONNECT 超时)。
---
@@ -49,7 +49,7 @@ flowchart TB
NP["netPoll<br/>INT# GPIO 轮询 → 全局中断 → 各 Socket SINT → 状态缓存全量刷新 + sock_reset 步进 → 每轮一批"]
T1["① netListenerRetryTick<br/>D10 重监听重试:30ms / 2s 时限"]
T2["② netPendingTick<br/>L3 挂起 recv:①数据 ②到点(仅显式预算) ③断连 ④tripwire 60s 只留痕"]
T3["③ netReconcileTick<br/>Trap 22 对账 + 监听重建合一(N34 阶段 3):100ms / 分级去抖 / 冷却<br/>读驱动状态缓存(①②③④⑤ + U11)"]
T3["③ netReconcileTick<br/>TRAP-NET-17 对账 + 监听重建合一(N34 阶段 3):100ms / 分级去抖 / 冷却<br/>读驱动状态缓存(①②③④⑤ + U11)"]
DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT · DROP_CONN"]
subgraph LK["_locked / 内部分派目标(仅 netTask 上下文)"]
LL["netListenLocked<br/>初次监听(open 重试 3× + sock_reset 排渣)"]
@@ -59,7 +59,7 @@ flowchart TB
LA["netAcceptLocked<br/>先 netPoll + 状态判定"]
LC["netCloseLocked<br/>了结挂起 + cmd_done 快路径 / sock_reset(GRACEFUL)"]
DC["netDropConnLocked<br/>拆会话保监听:sock_reset(QUARANTINE) + 即时重监听"]
RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + Trap 23 排渣隔离"]
RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + TRAP-NET-18 排渣隔离"]
end
EV["事件处理器<br/>handleConnect · handleDisconnect<br/>handleRecv · handleSend · handleTimeout"]
ST["netListenerSettle<br/>只置沉降/隔离事实(100ms / 400ms)"]
@@ -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 22)。**注意职责边界(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,N34 阶段 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`。
@@ -109,7 +109,7 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `net_init(ip,mask,gw,mac)` | 芯片一次性配置:探测/复位 → 参数 → MAC → 协议栈初始化 → 8 Socket 缓冲布局 → 使能 PING | `net_pending_cancel_all` → `ch395f_check_exist` / `ch395f_reset` / `set_fun_para(0x08)` / `set_tcp_mss` / `set_retrans_count+period` / `set_keepalive_idle+intvl+cnt` → `set_mac_addr` → `parse_ip_string` + `set_ip_addr` / `set_gwip_addr` / `set_mask_addr`(或 `set_dhcp`)→ `ch395f_init` → `set_send_buf` / `set_recv_buf` ×8 → `ping_enable` | ① **重建前**先取消全部挂起 recv 并逐一通知(不通知 = 调用方永久挂死),且必须早于控制块清零与芯片复位;② 重传/KeepAlive/**MAC**/IP/GW/MASK 参数**必须**在 `ch395f_init` 之前下发(`docs/芯片手册/CH395F.md` §9.2.1:SET_MAC 为步骤①,INIT_CH395 加载 MAC——顺序反了本次上电不生效,P1-7);③ `fun_para=0x08`:bit3 SOCKET_CLOSE(DISCONNECT/TIMEOUT 后不清缓冲,软件排空后手动关);bit0/bit1 保持 0——8 个 Socket 由索引 `_SN` 寻址 + 缓冲显式分配实现,与 FUN_PARA 无关(bit0 是保留位,必须为 0);④ 缓冲布局每 Socket 6 块(收 4 + 发 2)×8 = 48 块恰好用尽,Socket 0~7 必须显式重分配(Trap 01/02);⑤ 捕获 `xTaskGetCurrentTaskHandle` 存入 `s_net_task_handle`(防自通知死锁的判据) |
| `net_init(ip,mask,gw,mac)` | 芯片一次性配置:探测/复位 → 参数 → MAC → 协议栈初始化 → 8 Socket 缓冲布局 → 使能 PING | `net_pending_cancel_all` → `ch395f_check_exist` / `ch395f_reset` / `set_fun_para(0x08)` / `set_tcp_mss` / `set_retrans_count+period` / `set_keepalive_idle+intvl+cnt` → `set_mac_addr` → `parse_ip_string` + `set_ip_addr` / `set_gwip_addr` / `set_mask_addr`(或 `set_dhcp`)→ `ch395f_init` → `set_send_buf` / `set_recv_buf` ×8 → `ping_enable` | ① **重建前**先取消全部挂起 recv 并逐一通知(不通知 = 调用方永久挂死),且必须早于控制块清零与芯片复位;② 重传/KeepAlive/**MAC**/IP/GW/MASK 参数**必须**在 `ch395f_init` 之前下发(`docs/芯片手册/CH395F.md` §9.2.1:SET_MAC 为步骤①,INIT_CH395 加载 MAC——顺序反了本次上电不生效,P1-7);③ `fun_para=0x08`:bit3 SOCKET_CLOSE(DISCONNECT/TIMEOUT 后不清缓冲,软件排空后手动关);bit0/bit1 保持 0——8 个 Socket 由索引 `_SN` 寻址 + 缓冲显式分配实现,与 FUN_PARA 无关(bit0 是保留位,必须为 0);④ 缓冲布局每 Socket 6 块(收 4 + 发 2)×8 = 48 块恰好用尽,Socket 0~7 必须显式重分配(TRAP-NET-01/-02);⑤ 捕获 `xTaskGetCurrentTaskHandle` 存入 `s_net_task_handle`(防自通知死锁的判据) |
| `net_get_generation()` | 读网络代次号(纯 RAM) | 无 | FTP/UI/对时服务据此感知"网络已重建"→ 放弃旧监听并重建 |
| `net_generation_inc()` | 代次号 +1 | 无 | 唯一合法递增点 = `net_stack_bring_up()` 末尾(PHY/链路就绪后)——太早递增会让服务在网络不可用时抢跑重建;模拟器端恒为 0(无重建概念,上层自愈逻辑空转) |
@@ -117,7 +117,7 @@ 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 08);SENBUF_FREE 只置 `send_ready` 不记事件;状态缓存**唯一刷新点**在此(reconciler 只读缓存,E2 实测单次回读 18.1µs);`sock_reset_poll` 步进使同步包装 600ms 放弃后的复位机不搁浅(IDLE 槽纯 RAM 判零 SPI) |
| `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 内自然成立) |
### 4.3 经队列的公共 API 及其 netTask 侧实现
@@ -128,8 +128,8 @@ flowchart TB
|---|---|---|---|---|---|
| `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_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 14/17);目的 IP 字节序写反 = CONNECT TIMEOUT(Trap 16);返回 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 20:写穿 FIFO = 数据错位);`send_ready` 门控用 `net_poll` 轮询而非 osDelay;短写**必须**告警——静默截断会让对端永久等待剩余字节(2026-09-21 实证:UI 推流应答截断 → 客户端失联) |
| `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` 是跨任务共享全局量,随时被别的任务覆盖) |
@@ -177,11 +177,11 @@ flowchart TB
|---|---|---|---|
| `net_listener_settle` | 登记"本 fd 会话已结束,重建前需沉降/隔离"(**只置事实**) | 写 `s_relisten_quarantine[fd]`(等级:GRACEFUL 100ms / QUARANTINE 400ms) | N-32 P1:取代此前外部直接写 `s_relisten_quarantine[]`;由 `handle_disconnect_event`(GRACEFUL)/ `handle_timeout_event`(QUARANTINE)调用。**只置事实,动作交 reconciler 形态③④ / `net_listener_try`**;越界 fd 静默忽略 |
| `net_listener_try` | 尝试重建一次;失败登记重试(**不阻塞**) | `net_relisten_locked` → 成功 `net_relisten_done` / 失败置 `s_relisten_wait/next/dead` | 已在重试队列 → 直接返回 -1(**不重置重试时刻**):否则 10ms 一次的 accept 轮询会不断把重试时刻往后推 → 永不重试(饥饿) |
| `net_relisten_locked` | 真正的 open + listen(**单次尝试**) | (沉降期)`net_drain_chip_rx` + `close_socket` + `get_sock_int_status` / `get_glob_int_status_all` + `get_socket_status` → `set_proto_type` + `set_sour_port` + `open_socket` + `tcp_listen` + 清中断 | ① **严禁 osDelay 忙等**:失败只清理并返回,沉降交给重试轮询;② **Trap 23 沉降门**:带沉降标记的 socket(异常释放 TIMEOUT → QUARANTINE 400ms;正常释放 DISCONNECT → GRACEFUL 100ms)→ 排空 RX 残渣 + 主动 close + 排空中断 + 等芯片回 CLOSED/TCP_CLOSED;**上限强制放行**(必须 < 2s 重试时限,否则沉降耗尽重试预算 → `relisten give up` 监听被弃——2026-09-23 实测修正);③ 成功只置 state=LISTENING,**不 memset**(fd 与 `auto_relisten` 保留——BSD 风格监听常驻) |
| `net_relisten_locked` | 真正的 open + listen(**单次尝试**) | (沉降期)`net_drain_chip_rx` + `close_socket` + `get_sock_int_status` / `get_glob_int_status_all` + `get_socket_status` → `set_proto_type` + `set_sour_port` + `open_socket` + `tcp_listen` + 清中断 | ① **严禁 osDelay 忙等**:失败只清理并返回,沉降交给重试轮询;② **TRAP-NET-18 沉降门**:带沉降标记的 socket(异常释放 TIMEOUT → QUARANTINE 400ms;正常释放 DISCONNECT → GRACEFUL 100ms)→ 排空 RX 残渣 + 主动 close + 排空中断 + 等芯片回 CLOSED/TCP_CLOSED;**上限强制放行**(必须 < 2s 重试时限,否则沉降耗尽重试预算 → `relisten give up` 监听被弃——2026-09-23 实测修正);③ 成功只置 state=LISTENING,**不 memset**(fd 与 `auto_relisten` 保留——BSD 风格监听常驻) |
| `net_listener_retry_tick` | 每轮到点重试(D10) | `net_relisten_locked` → `net_relisten_done` / 顺延 / 放弃 | **同 fd 有在途挂起 recv 时跳过**(重开 Socket 会摧毁在途连接,L3 保险);到 2s 时限 → 清状态 + `relisten give up` 错误日志(应用可见) |
| `net_relisten_done` | 标记完成 | `net_listener_clear` +(探针构建)恢复计时样本 | 恢复时延 = 首次尝试 → 完成(L3 探针 `relisten` 段) |
| `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 23:中止/异常断开时芯片 RX 缓冲残留上一条连接的数据(实测 ~2048B),不排空会被下一条连接当自己的数据读走(静默截断);分块读避免新增静态缓冲(内存预算紧张) |
| `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)
@@ -203,7 +203,7 @@ flowchart TB
> `session_dead` **不在此清零**(与 `net_relisten_locked` 同一契约:重建监听 ≠ 应用已确认会话结束;清零只发生在 `handle_connect_event`)。
### 4.8 Trap 22 周期对账 + 监听重建(合一 `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):
@@ -231,9 +231,9 @@ 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_recv_event` | 置 `readable` + 回调;**不读 recv_len** | `fire_event` | 不读长度是为了避免与 `net_recv_locked` 的 `ch395f_get_recv_len` 竞争(Trap 13:分批读会归零);`readable` 供 `net_select` 无 SPI 访问判读 |
| `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 23);LISTENING → 仅记日志 | **`net_listener_settle(fd, NET_LISTENER_SETTLE_QUARANTINE)`** + `fire_event` | TIMEOUT = 异常释放(如客户端中止上传):对端在途数据随后落进 RX 缓冲 → 必须隔离(400ms)后才许复监听;正常 FIN 释放走 disconnect 路径(GRACEFUL 100ms),**不走**隔离 |
| `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),**不走**隔离 |
| `fire_event` | 调用已注册回调 | `p_sock->event_cb` | **回调上下文 = netTask**:回调内不得阻塞、不得发网络操作(会自等死锁) |
### 4.10 工具函数
@@ -295,7 +295,7 @@ sequenceDiagram
Note over N: close / 网络重建 / 陈旧 resync → 显式 settle/cancel(必通知)
```
### 5.3 断连 → 自动重监听(含 Trap 23 沉降)
### 5.3 断连 → 自动重监听(含 TRAP-NET-18 沉降)
```mermaid
sequenceDiagram
@@ -312,12 +312,12 @@ sequenceDiagram
end
Note over N: 只置事实 —— 重建统一由 net_reconcile_tick 形态③④(100ms)决定
N->>C: net_listener_try → net_relisten_locked
opt 沉降期内(Trap 23)
opt 沉降期内(TRAP-NET-18)
N->>C: 排空 RX 残渣(256B 分块)+ close + 排空中断
C-->>N: 未到 CLOSED/TCP_CLOSED → 返回(30ms 后重试轮询复检)
end
N->>C: set_proto_type + set_sour_port + open_socket + tcp_listen
alt open 失败(Trap 09 沉降)
alt open 失败(TRAP-NET-09 沉降)
N->>N: 登记重试(30ms 节奏 / 2s 时限)→ net_listener_retry_tick 到点重试
else 成功
N->>N: state = LISTENING(auto_relisten 保留)
@@ -326,7 +326,7 @@ sequenceDiagram
Note over N: 2s 超限时 → 清状态 + "relisten give up"(错误日志,应用可感知)
```
### 5.4 丢中断自愈(Trap 22 对账 + 监听重建合一 reconciler,N34 阶段 3)
### 5.4 丢中断自愈(TRAP-NET-17 对账 + 监听重建合一 reconciler,N34 阶段 3)
```mermaid
sequenceDiagram
@@ -367,14 +367,14 @@ sequenceDiagram
| # | 铁律 | 实证来源 |
|---|---|---|
| 1 | **一切芯片访问收敛 netTask**(唯一执行者);跨任务调用走队列 | Trap 16:跨任务 SPI 竞争破坏芯片寄存器 → 连接必超时 |
| 2 | **结果只经任务通知值回传**;消息按值入队,调用方看不到 netTask 对消息的写入;勿给消息加"回传用"字段 | Trap 10:6 个 API 永远返回 -1;"加 msg.err 字段"的修法曾致 `ui_net_sink` 每次空轮询误判真错误 → 连上即断 |
| 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、期间重连被拒 |
| 4 | **netTask 不向自身发消息 / 不自我挂起**(挂起登记对执行任务自身发起直接拒绝) | 自等自通知 = 死锁 |
| 5 | **基于状态读的重建动作必须去抖 + 冷却**:分级门槛 ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(100ms/轮)+ 冷却 1s | 2026-09-23:无去抖 → 监听反复重建直至 `relisten give up`,三服务口全死 |
| 6 | **异常释放(TIMEOUT)后复监听前必须隔离**:排空 RX 残渣 + 等 CLOSED/TCP_CLOSED;隔离上限 < 重试放弃时限 | Trap 23:~2KB 残渣被下条连接当自己的数据 → 首笔上传截断 2048B 并误报 226 成功;上限=2s 时隔离耗尽重试预算 → 监听被弃 |
| 7 | **RECV 电平触发中断每轮只处理一批** | Trap 08:连续循环 → 主循环饿死 |
| 8 | **close 前先了结挂起 recv + 排空残留 DISCONNECT** | Trap 14/17/19:锁存的陈旧 DISCONNECT 秒断下一连接 |
| 6 | **异常释放(TIMEOUT)后复监听前必须隔离**:排空 RX 残渣 + 等 CLOSED/TCP_CLOSED;隔离上限 < 重试放弃时限 | TRAP-NET-18:~2KB 残渣被下条连接当自己的数据 → 首笔上传截断 2048B 并误报 226 成功;上限=2s 时隔离耗尽重试预算 → 监听被弃 |
| 7 | **RECV 电平触发中断每轮只处理一批** | TRAP-NET-08:连续循环 → 主循环饿死 |
| 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 就谁重建"的竞争与重建风暴 |
@@ -395,11 +395,11 @@ 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 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_*`(N34 阶段 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 20) |
| `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` **故意不读**(防竞争,Trap 13) |
| `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_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 底 |
@@ -428,7 +428,7 @@ sequenceDiagram
- `docs/网络/BSD_Socket_API_使用指南.md` —— 公开 API 语义、示例、常见问题(本文的上半部)
- `docs/子系统设计/网络Socket监听生命周期设计说明.md` —— **监听生命周期权威设计**(N-32:边沿→电平、单一所有者重建(N34 阶段 3 起为 `net_reconcile_tick` 形态③④)、`net_listener.c` 模块化边界 §10)
- `docs/问题记录/CH395F_Trap_Records.md` —— Trap 01~23 机制细节(本文引用处)
- `docs/问题记录/CH395F_Trap_Records.md` —— TRAP-NET-01~23 机制细节(本文引用处)
- `docs/网络/Net层阻塞模型架构设计.md` —— 阻塞等待模型的现状架构设计(pending 请求模型 / 五路完成语义)
- `docs/问题记录/错误码传播审计.md` —— E-02 头阻塞与错误码回传全审计
- `docs/测试说明/链路延迟测试说明.md` —— D1/D10/L3/D14 等时延探针与自愈路径的台架证据
@@ -62,7 +62,7 @@ netTask 手里抢走(正是 B3 事故的形状,见 F-DRV-2)。缺的不是
## 2 Trap 根因归类总表
台账共 **24 条**(编号 01–25,**Trap 19 从未存在**,属悬空引用)。根因类别:
台账共 **24 条**(旧编号 01–25,**旧 19 从未存在**,属悬空引用)。根因类别:
**(a)** 芯片真实行为(不可在固件侧根治);**(b)** 固件寄存器值/常量错误;**(c)** 引用错误
(字节序/偏移/索引/单位);**(d)** 时序误用(顺序/忙等/沉降);**(e)** 设计缺口;**(f)** 判读或台架假象。
@@ -76,29 +76,29 @@ netTask 手里抢走(正是 B3 事故的形状,见 F-DRV-2)。缺的不是
| 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 25 实证窗口不定长)→ 重试保留,**应用层重复重试可删** |
| 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 23 实测伪 DISCONNECT 仍投递给下一条连接) |
| 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 02 的缓冲分配 | 否 | 驱动侧残留:语义宏未被使用(F-DRV-6) |
| 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 25 裸机台架独立实证芯片丢中断);但命中数应重测 |
| 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 条;另 Trap 12 非 CH395F、Trap 19 不存在。
(f) 判读假象 3 条、(d) 时序误用 2 条、(e) 设计缺口 2 条;另旧 12 非 CH395F、旧 19 不存在。
**催生五层自愈补丁的 6 条(09/14/17/22/23/25)中,只有 Trap 09 的
"启动形态"和 Trap 25 的"②accept 竞态"被根因修复消掉;其余全部落在芯片行为上,不可从根上消除。**
**催生五层自愈补丁的 6 条(09/14/17/22/23/25)中,只有 TRAP-NET-09 的
"启动形态"和 TRAP-NET-20 的"②accept 竞态"被根因修复消掉;其余全部落在芯片行为上,不可从根上消除。**
## 3 五层自愈 × 驱动层契约
@@ -106,9 +106,9 @@ netTask 手里抢走(正是 B3 事故的形状,见 F-DRV-2)。缺的不是
|---|---|---|---|
| `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 22 周期对账 | net_socket.c:544-546(200ms/去抖 2/冷却 1s)、:548-641 | 驱动无状态缓存 → 对账只能每周期重新发 SPI | **保留**(根因是芯片丢中断);参数与 pump 重复,应收敛(F-DRV-1) |
| 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 23 排渣隔离 | 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) |
| 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) |
## 4 驱动层缺口清单(缺什么原语)
@@ -146,14 +146,14 @@ net_listener.c:516(pump 100ms/去抖 2/冷却 1s,:437-441)。驱动 static
net_socket.c:1122-1137 用注释收口("原为遍历 8 个 Socket 全清 + 再读清全局"→ 仅清本 Socket),
git 考古链 `e862009bc`→`319d00e3`→`db64e6acf`(commit `4f2cc8b` 修)。
影响:同类事故(吞掉其它在用 Socket 的 CONNECT/DISCONNECT 边沿事件)可在新调用点复发;
这正是"看起来像 Trap 22 的自家 bug"的来源。
这正是"看起来像 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 07 定案更正第 3 条正是把这类读数读成"卡在某个 TCP 态"。
TRAP-NET-07 定案更正第 3 条正是把这类读数读成"卡在某个 TCP 态"。
影响:幽灵态/孤儿态判定的正确性依赖每个调用点都记得这条规则——已三次失守。
裁决:**下沉驱动**——返回 `{open, tcp_state}` 结构 + `ch395f_sock_is_listening()/is_established()` 谓词,
`open=0` 时 TCP 态返回"无意义"哨兵值。
@@ -166,8 +166,8 @@ net_listener.c:149-204(排空 RX→close→读 SINT→读全局→读状态→
裁决:**下沉驱动**——`ch395f_sock_reset(sn, mode, wait_ms)`(mode=异常/正常),
net 层只选 mode。与 T1 的 F-NET-9 同判,签名归 T4 定。
**F-DRV-5 · IP 字节序契约未封装,且驱动注释与实测相反(Trap 16 的复发面仍在)**
证据:Trap 16 根因是照抄手册"所有 IP 命令低字节在前"的总括,实测 SET_IP/SET_DES_IP 收**点分序**;
**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)。
@@ -178,13 +178,13 @@ net 层只选 mode。与 T1 的 F-NET-9 同判,签名归 T4 定。
**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 18 的"bit0=1 多 Socket 模式"伪根因能存活一年多,正是因为语义没有类型化到宏/枚举层,
影响: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 20)→ 只告警不拦截,
证据: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`"从注释升格为
@@ -203,7 +203,7 @@ net 层只选 mode。与 T1 的 F-NET-9 同判,签名归 T4 定。
证据: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 09 的"启动形态"已被就绪屏障 + 启动期等 PHY link 消除(net_task.c:118-130/:232),
影响:TRAP-NET-09 的"启动形态"已被就绪屏障 + 启动期等 PHY link 消除(net_task.c:118-130/:232),
应用层重试的原始理由已不存在;保留它只会把恢复时间拉长并制造额外的重建所有者。
裁决:**简化**——net 层两处并入 D10(P2b);应用层两处**可删**(保留 lftpd 的重试前先确认
`net_relisten` 语义已覆盖,见 T1 的裁决)。
@@ -214,13 +214,13 @@ lftpd.c:473-477(3 轮 ×200ms)。
影响:换料/降版本时 `0x08` 静默失效,而整套排空契约(bit3=1)正是建立在 bit3 生效之上。
裁决:**简化**——`net_init` 内门控(版本不足则拒绝置 bit3 并留自检记录),一行判定消掉整类静默。
**F-DRV-11 · Trap 13 的"必须一次读空整个接收缓冲"约束已事实作废,但注释仍在四处约束后人**
证据:ch395f.h:146("接收方向需一次性读空整个 Socket 接收缓冲…避免 Trap 13 部分读导致剩余数据不可见")、
**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),Trap 21 结案已撤回"部分排空致命",Trap 23 回归 10/10+5/5 全在部分读下通过。
(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)
仍挂在一条未证实的机理上。
裁决:**可删**(注释级)——把 Trap 13 降级为"未证实的历史观察",补一条"部分读→再查 recv_len"
裁决:**可删**(注释级)——把旧机理降级为"未证实的历史观察",补一条"部分读→再查 recv_len"
定向 TC 结案;DMA 缓冲 sizing 改以"接收缓冲 2KB"为唯一理由。
**F-DRV-12 · 死声明与零调用 API 清单(驱动 API 面虚胖)**
@@ -230,10 +230,10 @@ net_socket.c:1908/:1969/:3466/:3470。而 `net_recv_locked` 常态按 `min(len,
`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 07 的"disconnect→close"错误序列正是从
影响:API 面越大,"哪个是产品路径"越难判;TRAP-NET-07 的"disconnect→close"错误序列正是从
一个零调用的 API 出发的。
裁决:**可删/登记**——删死宏与未实现命令声明;`tcp_disconnect` 保留但注释标注"产品路径禁用";
`clear_recv_buf` 保留(Trap 25 已证"缓冲重分配无效",排渣用读空而非 CLEAR_RECV_BUF)。
`clear_recv_buf` 保留(TRAP-NET-20 已证"缓冲重分配无效",排渣用读空而非 CLEAR_RECV_BUF)。
**F-DRV-13 · 链路事件静默:四个全局中断位读清后无人消费,驱动无链路缓存**
证据:net_socket.c:441 读全局后只按 `bit4..bit11` 分派 socket 事件(:464),
@@ -268,15 +268,15 @@ gpio.c:170-171、stm32f4xx_it.c:391);消费节奏 = net_task.c:241-279 主
| 机制 | 能否因"驱动持事实"而删 | 理由 |
|---|---|---|
| Trap 22 对账①② | **不能删,但可大幅收窄** | 根因是芯片丢中断(Trap 25 裸机台架独立实证)。驱动持缓存后,对账的"回读"部分变成读缓存;"发现失配"仍需存在,只是从"周期 SPI 轮询"降为"每 poll 周期一次刷新 + 比对" |
| 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 23 排渣隔离 | **可下沉** | 排空/等待是纯芯片机械;net 层只保留"异常/正常"两档 mode |
| TRAP-NET-18 排渣隔离 | **可下沉** | 排空/等待是纯芯片机械;net 层只保留"异常/正常"两档 mode |
| 探针 `net_lost_connect_probe` | **不能删,可去抖简化** | 丢 CONNECT 是芯片行为;驱动持事实后去抖可去掉(读缓存无瞬态噪声),保留"状态已 ESTABLISHED 而本层 LISTENING → 补 accept" |
| `auto_relisten` | **不能删** | 芯片永不自动回 LISTEN,这是模型承诺 |
**一句话结论**:五层里有四层(对账、pump、探针、resync)存在的直接理由是"驱动不持事实、net 层只能自建镜像";
把它们删掉的前提不是"根因已修",而是"驱动成为唯一事实源"。Trap 23 的排渣隔离则相反——
把它们删掉的前提不是"根因已修",而是"驱动成为唯一事实源"。TRAP-NET-18 的排渣隔离则相反——
它的根因(在途数据落 RX)不可修,但它的**实现位置**应当从 net 层下沉到驱动。
## 7 对 T4 汇总的输入
@@ -284,7 +284,7 @@ gpio.c:170-171、stm32f4xx_it.c:391);消费节奏 = net_task.c:241-279 主
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(撤 Trap 13 注释约束)、
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 同一实验,勿重复做)。
+13 -13
View File
@@ -1,7 +1,7 @@
# N34 自愈审计 · 汇总报告(T4)
> 审计对象:BSD Socket API 的「芯片/本层状态对账与重监听自愈」五层机制(auto_relisten / D10 重试 /
> Trap 22 对账 / N-32 pump / Trap 23 排渣隔离)及其显式 API(`net_relisten` / `net_resync_listen`)。
> TRAP-NET-17 对账 / N-32 pump / TRAP-NET-18 排渣隔离)及其显式 API(`net_relisten` / `net_resync_listen`)。
> 方法:三路独立审计汇总——net 层盘点(`docs/网络/N34-自愈审计/net层自愈机制盘点.md`,F-NET-1..14)、
> CH395F 驱动层审计(`docs/网络/N34-自愈审计/CH395F驱动层审计.md`,F-DRV-1..16)、
> 主流方案对照(`docs/网络/N34-自愈审计/主流方案对照.md`,F-MS-1..9,含 T4 更正两处:多连接模式未启用、
@@ -21,10 +21,10 @@
| `b8c6a3d` | **阶段 1** API 面收口(对账⑤ + 删 3 API + time_sync/lftpd) | E10:全绿回归(详见实验方案 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 22/23/25、E-02 双 PASS、104、对时、integrity 15/15) |
| `123d80a` | E6 插桩 `NET_U15_PROBE`(默认关,零成本) | 24h 挂机进行中(阶段 3 项 4 门禁) |
| `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 观察项) |
阶段 0~3 已完成并转正;在途仅 **E6 挂机**(裁决项 4 U15 内化做不做)与延后项
阶段 0~3 与 E6 均已完成并转正;仅剩延后项
(relisten 沉降段统一,见实施方案 §5.2)。阶段 1 实施过程中的一条关键台架发现
已留痕(实验方案 E10):**`net_listener_armed` 必须放行 ESTABLISHED**——单连接模型
的"待 accept"电平,漏放行时 i104(200ms 周期)抢不过对账② 的 ~400ms 去抖而整场
@@ -35,12 +35,12 @@
**问:「芯片/本层状态对账与重监听自愈」可以简化或删除吗?可以内聚到 ch395f 驱动层吗?**
1. **不能整体删除。** 催生五层自愈的 6 条 Trap(09/14/17/22/23/25)中,只有 2 个被根因修复消掉
(Trap 09 的启动形态→就绪屏障;Trap 25 的②accept 竞态→closed-with-data)。其余全部落在**芯片行为**上:
Trap 25 的裸机芯片台架(剥离 OS/net/FTP)独立复现"握手完成但 CONNECT 中断被丢"——这是
(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 16 字节序、Trap 02 缓冲重叠、Trap 20 FIFO 上限、B3 全量读清),但它们**不是**五层自愈的成因——
(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),
@@ -72,9 +72,9 @@ T1(net 层视角)判"机制主体保留、API 可删";T3(主流对照视
|---|---|---|---|
| `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 22 `net_reconcile_tick` | net_socket.c:548-641 | **功能保留,形态重构**(阶段 1 已做①扩展=⑤) | 与 pump 判据合并为**单一 reconciler**、读驱动缓存、一套去抖(F-DRV-1);①扩展覆盖"本层 ESTABLISHED+芯片已回 LISTEN"缺口(F-NET-2)✅ 已实施(bad=5,`b8c6a3d`) |
| 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 23 排渣隔离 | 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 |
| 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 整场饿死(实验方案 E10) |
@@ -83,15 +83,15 @@ T1(net 层视角)判"机制主体保留、API 可删";T3(主流对照视
| `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 09 沉降窗(F-NET-13);改 lftpd 模式同 fd 回 accept。实测连续 6 轮推送正常(E10) |
| lftpd 应用层阻塞重试 | lftpd_inet.c:39-70、lftpd.c:473-477 | **删除/简化** ✅ 已实施 | Trap 09 启动形态已被就绪屏障消除,重试理由不存在(F-DRV-9)。实施偏差:未走 `net_listener_try` 登记(会把 BOUND/D10 状态语义一并拖入),改单次尝试+失败即 425(E10 登记) |
| 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) |
## 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 09/23 参数);非阻塞 `cmd_done()`;链路态缓存+边沿事件;IP 字节序封装;版本门控 | 驱动零状态(4 个 static);读即清裸暴露;命令原语全阻塞(`HAL_Delay(20)` 忙等、4s 上限);字节序注释与实测相反 |
| `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 重试——三层兜底散在应用 |
@@ -101,7 +101,7 @@ T1(net 层视角)判"机制主体保留、API 可删";T3(主流对照视
## 4. 分阶段路线(风险从低到高;每阶段独立可回退)
- **阶段 0 · 零行为风险清理**(纯注释/宏/一行判定,可先行提交):
F-DRV-5 字节序注释更正、F-DRV-6 FUN_PARA 用宏、F-DRV-11 撤 Trap 13 注释约束、F-DRV-12 删死声明、
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`
@@ -3,7 +3,7 @@
> 审计对象:`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 09/22/23/25 与 2026-10-11 寄存器审计章节 B1~B10 条目)、
> 参照文档: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(五层自愈口径)。
@@ -14,24 +14,24 @@
| # | 机制 | 代码位置 | 触发条件 | 改本层哪些状态 | 发哪些芯片命令 | 定时器/去抖/冷却 | 重叠/互为兜底的对象 | 根因分类 | 初步裁决 |
|---|---|---|---|---|---|---|---|---|---|
| 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 09 PHY 形态 + Trap 25 裸机台架实证拆链窗口 >2s 不定长);旧实现 osDelay(500) 忙等属 (b) 已修(e5733b3) | **保留**;"give up" 语义需简化(F-NET-4) |
| 3 | **Trap 22 周期对账** `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 25 定案"探针快路径 + 对账兜底网");① 与 `net_resync_listen` 语义相邻但判据不同(见 F-NET-2);原③④ 已移交 pump(:536-537/606-607) | (a) 芯片丢 CONNECT/DISCONNECT 中断(Trap 22 生产实测 + Trap 25 裸机台架独立实证,**与 B3 固件 bug 无关**);幽灵态 (a) | **保留**;建议扩展①(F-NET-2) |
| 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 23 排渣隔离** `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 22 快速复连时序而设(两机制互相制衡) | (a) FUN_PARA bit3=1(0x08,D4 定案必须保留)下芯片保留现场 + 在途数据在 TIMEOUT 之后才落缓冲 + 残留 DISCONNECT 投递给下一条连接(Trap 23 两阶段台架实证;0ms/30ms 沉降无效、100ms 通过) | **保留**;"close+排空+等 CLOSED"原语可**下沉**驱动层合并(F-NET-9) |
| 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)(Trap 13 FIN 先于数据、bit3=1 保留现场);看门狗属 (c) 完成逻辑缺位的 tripwire | **保留**(含看门狗,留痕成本极低);它是"消费者驱动重建"三处之一,P2a 已定案不可删 |
| 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 25 双信源探针) | 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 25 三次定案:425 真根因是我方 relisten 自毁缓冲) | 保留 |
| `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 23 硬化节留痕) | 保留 |
| `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 14/17 滞后锁存 | 保留 |
| 陈旧 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 全灭是事实) | 保留 |
## 2. 根因分类汇总
@@ -41,22 +41,22 @@
| 芯片行为 | 证据 | 支撑的机制 |
|---|---|---|
| 连接结束后芯片永不自动回 LISTEN(单连接复用模型下监听槽位被消耗) | 设计文档第 2.1 节引手册;官方 EXAM10 例程在事件现场当场重建 | auto_relisten、pump、D10、resync 语义 |
| 快速复连/拆链窗口内丢 CONNECT/DISCONNECT 中断 | Trap 22 生产实测;**Trap 25 裸机台架剥离 OS/net 层仍复现**(关键:证明与固件层 bug 无关) | 对账①②、探针、pump |
| 拆链收尾窗口变长且不可观测(>2s 不定长),窗口内假报 ESTABLISHED(幽灵态,对端端口=旧连接) | Trap 25 定案实验 | 幽灵甄别 `last_peer_port`、去抖+冷却纪律 |
| FUN_PARA bit3=1(0x08,D4 定案保留):DISCONNECT/TIMEOUT 后芯片不关 Socket 不清缓冲(停 0x05/0x00),MCU 必须排空后显式 close | 手册 §5.52②/§5.23;Trap 25 三次定案判别探针(recv_len=1027 完整载荷) | closed-with-data accept、Trap 23 排渣、close_locked 排空循环 |
| 中止时段的在途数据在 TIMEOUT **之后**才落进 RX 缓冲(~2KB 残渣被下条连接读走) | Trap 23 一阶段(RST 10/10 复现→修复 10/10) | QUARANTINE 400ms + 持续排空 |
| 上一条连接残留 DISCONNECT 会在复用同 fd 的下一条连接 accept 后 10~20ms 投递 | Trap 23 二阶段(0ms/30ms 沉降无效、100ms 通过) | GRACEFUL 100ms(仅收过数据时登记) |
| close 后重开 Socket 需沉降(PHY 形态 + 拆链形态) | Trap 09;Trap 25 台架(固定沉降 100~3000ms 均证伪"等就好") | D10 到点重试(而非固定延时) |
| 快速复连/拆链窗口内丢 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 14 | net_poll 集中读清纪律;也解释了为何"丢弃式读全局"有害(F-NET-7) |
| 全局中断寄存器整体读即清、无按位清除;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 22 对账在生产中的部分命中 | **仍必要**:Trap 25 裸机台架(无 net 层代码)独立实证芯片自身丢中断;但 B3 修复后对账在生产日志的命中数应显著下降——可台架统计验证(F-NET-8) |
| 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 18:FUN_PARA bit0=1(保留位,无出处) | d758d5c 定案 0x08 | 多 Socket 模式误判相关 | 已修;对自愈机制无直接影响 |
| 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 重传超时已覆盖静默死亡) | — | 正面案例:根因复核后主动删除,应作为本次审计的裁决范式 |
@@ -92,7 +92,7 @@
**✅ 已实施(阶段 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 25 已实证该节奏对短命监听器"救不全")。
证据: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)删除。
@@ -109,13 +109,13 @@
建议:小修——两处 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 14/17/23 指定的两级预清,本次不动"。
证据: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 22 对账不是 B3 固件 bug 的补丁——裸机台架已独立实证芯片丢中断,故 B3 修复后对账不可删;但命中数应可测得下降**
证据:Trap 25 定案实验在**剥离 OS/net/FTP 的裸机芯片台架**上复现"握手完成但 CONNECT 中断被丢"(修复组 15/15,机制触发 5 次全救回);B3 修复在其后(4f2cc8b)。
影响:直接回答本次审计的核心疑问——"根因已修的补丁是否仍必要":对 Trap 22 而言,其根因是芯片行为而非固件 bug,**必要**。
**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 层有两套变体实现——下沉驱动层的最佳候选**
@@ -134,13 +134,13 @@
建议:改注释;顺带该 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 22 对账不含 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_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 09)+ 最坏 ~10s 端口下线"。对照正面样板:lftpd 控制通道明确不 close(lftpd.c:746-750/826-827)、iec104 EOF 分支带 `s_client != s_listen` 护栏(iec104_serv.c:259-262)。
影响::8888 每次会话结束都主动制造一次 Trap 09 沉降窗;也是"应用层还能毁监听器"的活例。
证据: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 沉降(异常收尾,非正常路径)。
@@ -156,14 +156,14 @@
- 丢 CONNECT:探针(accept 内联,快)↔ 对账②(周期,兜底网);两者共享幽灵甄别;:2404 因门控过期只剩②(F-NET-4)。
- 丢 DISCONNECT/静默回收:对账①(芯片 CLOSED 形态)↔ 对账⑤(芯片回 LISTEN 形态,阶段 1 起自动认领 = F-NET-2 已闭环)。
- 会话结束感知:session_dead 电平(P1)兜住"抢先 relisten 吞 EOF";pending_tick ③ 与 recv DONTWAIT 双路径给 EOF;看门狗兜"完成逻辑缺位"。
- 芯片收尾残留:Trap 23 沉降/隔离(重建前)↔ close_locked 排空循环(显式关闭)↔ listen/relisten 成功尾读本 Socket SINT——三处清理纪律,两套命令变体(F-NET-9 下沉候选)。
- 数据面丢中断:RECV_OK 电平重放(Trap 08)+ readable 门控 + 上层 32 轮盲收(F-NET-12,唯一未内聚的一层)。
- 芯片收尾残留:TRAP-NET-18 沉降/隔离(重建前)↔ close_locked 排空循环(显式关闭)↔ listen/relisten 成功尾读本 Socket SINT——三处清理纪律,两套命令变体(F-NET-9 下沉候选)。
- 数据面丢中断:RECV_OK 电平重放(TRAP-NET-08)+ readable 门控 + 上层 32 轮盲收(F-NET-12,唯一未内聚的一层)。
## 5. 裁决汇总
| 机制 | 裁决 | 一句话依据 |
|---|---|---|
| auto_relisten / session_dead / released_abnormal / 幽灵甄别 / closed-with-data / 探针 / 对账①② / pump / D10 / Trap 23 沉降隔离 / L3 pending + 看门狗 | **保留** | 根因是芯片行为或已实测裁决的架构解;Trap 25 裸机台架是"不可从根上消除"的判据 |
| 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 探针失效 |
@@ -187,22 +187,22 @@
放行的根因是**判据的证据通道被事件自身污染**:
1. 滞后 DISCONNECT 在**拆链收尾完成时刻**锁存(Trap 14:DISCONNECT 在 FIN/ACK 完成后才上报),而拆链收尾完成的标志正是把 TCP_CLOSED(0x00) 写进该 Socket 的状态寄存器。事件投递与寄存器写 0x00 是同一时刻的两个面——甄别读到的 0x00 无法区分「真断开」与「陈旧事件 + 新连接仍活着」。
2. 每 Socket 只有一个状态槽,存的是**最后一次状态迁移**(Trap 25 已证该寄存器在拆链窗口内双向不可信:既会假报 ESTABLISHED 幽灵,也会假报 CLOSED——新连接的 0x04 迁移被旧连接的 0x00 迁移覆盖)。E1 签名 `st=0x05/0x00` 即:Socket 仍 OPEN(0x05,被杀连接在芯片侧还活着),TCP 槽显示旧拆链的 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 23 二阶段与 Trap 25 现象 3 均有记载)。新连接自己的握手 ACK 流量往往正是完成旧拆链最后一次 FIN/ACK 交换的那笔流量 → 背靠背强相关。
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 20 时代标定的(当时事件先于寄存器写投递,芯片显示 0x04 可辨)。剩余可用判别轴只有两条:时间上的电平确认(对账去抖语义)或"年轻连接+零数据"启发式。
结论: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 14 锁存新的滞后 DISCONNECT → 下一条连接再被杀。最初的残渣只是种子,循环靠"杀→close→拆链→滞后事件"自我繁殖。
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 22 的 `: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 23 的 RST 中止修复从未循环过;循环是 DISCONNECT 有门路径独有的。
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 镜像门控为何错过
@@ -238,13 +238,13 @@
### 回归集建议
把 `n30_repro`(背靠背 STOR)纳入常备回归集——Trap 25 终版回归只含有间隔的 `ftp_regression`,背靠背形态不在集内,这是本次 9/10 能潜伏的测试面原因(实验方案对照台账已登记)。验收线建议:`n30_repro --n 50` 通过数与 `:21 @0ms×50`、`ftp_regression`、`ftp_abort_probe` 同时全绿,由台架定具体门槛。
把 `n30_repro`(背靠背 STOR)纳入常备回归集——TRAP-NET-20 终版回归只含有间隔的 `ftp_regression`,背靠背形态不在集内,这是本次 9/10 能潜伏的测试面原因(实验方案对照台账已登记)。验收线建议:`n30_repro --n 50` 通过数与 `:21 @0ms×50`、`ftp_regression`、`ftp_abort_probe` 同时全绿,由台架定具体门槛。
## F-NET-17 残余偶发的真身:风暴态(E8 深查,2026-10-13)
E7b 的"残余 ~3%"不是稳定值:同固件同负载,故障率随设备状态在 **2%(全新启动)~ 54%(残渣累积)~ 12%(仅复位)** 间摆动(实验方案 E8 矩阵)。"偶发"的真身是**可自持的风暴态**,三个缺陷叠加:
1. **沉降自再生锁存**(M1 的硬币):沉降段 `net_listener.c:174` close 之后,拆链收尾在 ~10-20ms 后(Trap 14 延迟锁存)**重新置位** DISCONNECT;`:176` 的 SINT 读清与 close 同轮发生,读不清这枚迟到的锁存 → 沉降最后一轮"清干净"的假象下,relisten 完成后 11ms 首条连接被杀。30ms 重试粒度 vs 10-20ms 锁存延迟 = 每次重建一次硬币——E7b 的 2% 即此。
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 轮"救回→杀→救回")见实验方案 E8 节。
@@ -261,11 +261,11 @@ E7b 的"残余 ~3%"不是稳定值:同固件同负载,故障率随设备状
**手册口径**:§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 07 定案更正,Trap 记录实测表)模式 C 正是本问题的受控实验——DISCONNECT 已发生、芯片停在 `0x05/0x00`(与风暴签名逐位相同),补 `close` → **+0 ms 回 CLOSED**、重开重连 5/5 OK;且"15 轮重开均未观察到残留 DISCONNECT(0x10)——close 顺带清掉了锁存"。即:**close 一个已 CLOSED 的 Socket 不产生新锁存,反而顺带清掉已锁存的位**。模式 A(close 一条活连接,主动拆链)5 轮同样无残留锁存。
**证伪强形式(干净情形)**: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 14 的张力消解**:Trap 14 的残留锁存不是 close 命令制造的,而是**拆链完成事件**——"DISCONNECT 可能在与 CLOSED 状态达成几乎同时、甚至之后才锁存"(Trap 记录 Trap 14 根因节),粘滞跨会话(B3 条目"SINT 粘滞到被读")。锁存的生成者是拆链过程(多为对端 FIN 的被动拆链收尾),不是 close 命令。
**与 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 25 定案)的被动拆链收尾迟到完成(Trap 14/23 的 10~20ms,Trap 25 的 >2s 不可观测窗);(b) 卡死 socket(0x05/0x00)把新 SYN 接进来又自行拆掉(`docs/芯片手册/CH395F.md` §5.29 语义域),每轮自产一对 CONNECT/DISCONNECT。
**风暴窗口的直接反证**: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);
@@ -304,7 +304,7 @@ if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) &&
1. lftpd accept 轮询见 ESTABLISHED → :1305 门通过 → **accept 正常返 fd**(不再依赖 closed-with-data 救回);
2. recv 快路径无状态门槛 → 消费者在 400ms 租约内排空 RX(引擎活则数据续到);
3. 对 n30 的小段 STOR:400ms 租约足以让传输完成 + 客户端正常 FIN(0x07 健康释放)→ **风暴链被"会话正常完成"掐断**;
4. 槽恢复 0x04 的形态(Trap 20 时代可恢复类):对账① bad=0 → 连接存活,flag 由 relisten/connect 清;
4. 槽恢复 0x04 的形态(TRAP-NET-16 时代可恢复类):对账① bad=0 → 连接存活,flag 由 relisten/connect 清;
5. 引擎真死:消费者排空后 WOULDBLOCK,对账①释放后得 EOF——EOF 晚 ~400ms,语义不变。
**:21 @0ms 快复连为何零误伤**:
@@ -341,6 +341,6 @@ OPEN: SET_PROTO_TYPE/SET_SOUR_PORT/OPEN/TCP_LISTEN + 成功尾(现 :207-267
```
- 参数建议:`LATCH_GRACE_MS=50`(实测锁存延迟 10~20ms,×2.5 余量)、`QUIET_ROUNDS=2`(2×30ms)、`RESET_MAX_MS=800`(保持"必须 < 2000 放弃时限"规则)、`hold_min` 沿用两级。
- **诚实边界(来自 T6-① 修正)**:静默不是有界保证——拆链完成可被**新连接自身流量**触发(Trap 25 窗口 >2s 不可观测),状态机只能静默"已在途的拆链";"未来流量触发的拆链"只有 M3 的电平确认能接住。**M5 与 M3 互补成对,不是替代关系**:M5 消灭"沉降窗内可静默"的绝大多数锁存(E7b 的 2% 硬币),M3 兜住无界残余(风暴态)。
- **诚实边界(来自 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 步即红线裁决点——"泵与救回互抢"在状态机内单点裁决,五入口不再各自竞速。
@@ -80,8 +80,8 @@
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 09 沉降窗口与
Trap 23 排渣隔离收进这一个同步原语(对应 ioLibrary `close()` 内联等待、FreeRTOS `listen()` 内联清流)。
`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′ 出局论证(多连接模式
@@ -92,13 +92,13 @@
| 现有机制 | 裁决 | 依据(主流对照) |
|---|---|---|
| Trap 22 周期对账 `net_reconcile_tick`(200 ms + 去抖 + 冷却) | **整体删除** | 无镜像即无对账;ioLibrary/mbed 决策即回读(P1) |
| 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 09 沉降收进 `ch395f_sock_reset` |
| 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 23 沉降/隔离状态机(`net_listener_settle` 400/100/800 ms 三档) | **下沉驱动**:并入 `ch395f_sock_reset(sn, mode, timeout)`;net 层只选 mode(异常/正常) | ioLibrary `close()` 内联等待、W5300 erratum 内联先例 |
| 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 同样单栈任务 + 阻塞事件模型) | — |
@@ -107,8 +107,8 @@
| 怪癖 | 任何架构都要处理 | 主流收法 |
|---|---|---|
| 丢中断(CONNECT/DISCONNECT/RECV_OK,INT 电平驱动 + IR 位重叠) | 是(W5500 官方笔记同款警告) | 事件唤醒 + **有界超时回读**,收在驱动层(Zephyr `k_sem_take` 超时、PeterBorisenko 500 ms);net 层不做周期对账 |
| close 沉降窗口(Trap 09:close 后立即 open 失败) | 是(ioLibrary `close()` 内 `while(Sn_SR != CLOSED)`;listen 竞态 issue 127) | `close()/socket()` 事务内联等待 + 有界超时(驱动原语内部) |
| RX 残渣(Trap 23:中止后残留 ~2 KB 被下一条连接读走) | 是(软栈对应物 = FreeRTOS reuse `listen()` 清流;硬栈 = 打开前 close/清缓冲) | `ch395f_sock_reset` 前置排空(`CLEAR_RECV_BUF_SN` 命令现成) |
| 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 层订阅;不进对账 |
@@ -116,7 +116,7 @@
本项目偏离主流的不是"单连接复用"(ioLibrary 无 accept、CH395 每 socket 一连接、FreeRTOS reuse 模式全部同形),
而是三件事的组合:**① net 层维护 8 态镜像并作为决策依据;② 状态迁移只靠中断事件推进;③ 监听重建与事务脱钩**。
镜像滞后 → 需要周期对账(Trap 22);事件丢失 → 需要探针与门控补偿;重建脱钩 → 需要 pump/重试/沉降三件套。
镜像滞后 → 需要周期对账(TRAP-NET-17);事件丢失 → 需要探针与门控补偿;重建脱钩 → 需要 pump/重试/沉降三件套。
按 P1~P6 重构后,五层中的四层(对账、pump、resync、probe)失去存在理由,一层(沉降/排渣)下沉为驱动原语。
## 5 编号发现
@@ -135,7 +135,7 @@
结论不变:`net_resync_listen` 这类"仅修 MCU 状态"的 API 是镜像滞后的直接产物,回读模型下无存在理由。
- **F-MS-5** FreeRTOS+TCP `FREERTOS_SO_REUSE_LISTEN_SOCKET` 给出与本项目模型同形的**主流参考实现**:
监听 socket 自身转连接、回 LISTEN 由栈的单一状态机在事务内完成;且 `listen()` **同步清空 rx/tx 流**——
这正是 Trap 23 排渣隔离的主流对应物(一行内联清理,而非 400/100/800 ms 三档状态机)。
这正是 TRAP-NET-18 排渣隔离的主流对应物(一行内联清理,而非 400/100/800 ms 三档状态机)。
- **F-MS-6** mbed WIZnetInterface 的 accept 模型:连接继承原芯片 socket,**accept 事务内联**在另一颗芯片 socket 上
bind+listen 重建监听(用芯片 8 socket 兑现"监听常驻"承诺)。其失败处理粗糙(可静默失去监听)说明:
内联重建仍需有界重试,但重试应长在事务/模块内部,而不是独立周期机制。CH395F 多连接模式已启用,该模型可移植。
+15 -15
View File
@@ -27,12 +27,12 @@
| M4-lite | 删沉降段全局读即清(模块约定违规点) | net_listener.c:178 | 断"沉降热路径搁浅他 Socket 事件"放大器(**必须与 M1 同批**,E7 实证) |
回归:n30 116/120(残余偶发自愈)+ ftp_regression 11/11 + :7003 50/50 + iec104 18/18 +
Trap 23 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见实验方案 E8 节与
TRAP-NET-18 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见实验方案 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 潜伏的测试面根因);
`MDK-ARM/ftp_abort_probe.py` 迁 `test/host/`。
`ftp_abort_probe.py` 已迁 `test/host/`(2026-10-13,N34 阶段 3 收尾,`rename_ref.py` 全库引用同步)。
## 2. 阶段 0 · 零风险清理(先行,不依赖实验)✅ 已完成(`4c0a0af`,E10 验证)
@@ -47,12 +47,12 @@ Trap 23 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见
|---|---|---|---|
| 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 22 快速复连子集) |
| 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 | Trap 13 注释降级为"未证实历史观察";DMA 缓冲 sizing 理由改"接收缓冲 2KB" | ch395f.h:143-153、net_socket.c:1908/:1969/:3466/:3470 | @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)。
@@ -80,7 +80,7 @@ Trap 23 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见
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 09 启动形态;PASV 监听失败改走 `net_listener_try` 登记。
就绪屏障已消除 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` 锚点。
@@ -163,18 +163,18 @@ Trap 23 RST/FIN 5/5+5/5 + @test PASS + 0 错 0 警。**E8 深查修正**(见
并**不再读清全局中断寄存器**(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 门禁暂缓**(E6 未给出"频率>0"的定案数据,按 Trap 25「不留未证明的复杂度」不动消费者盲收;
待 E6 专项窗口补数据后再裁决)。
✅ **E6 已裁决(2026-10-13,实验方案 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 22(:7003 快速复连 10/10、:21 100ms/0ms/600ms 各 50/50)、
Trap 23(RST 10/10 + FIN 5/5,SHA-256)、Trap 25(n30_repro 11/11)、E-02(e02_probe.py)、
验收:完整台架回归 = 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 22 `:7003` 50/50 +
`:21` 100ms/0ms/600ms 各 50/50;Trap 23 RST 10/10 + FIN 5/5;Trap 25 n30 11/11;E-02 双 PASS;
✅ **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`)。
@@ -189,17 +189,17 @@ E-02 的最后一环不是"挂起 recv 不冻结 netTask"(阶段 1.5 已解决
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 07 定案更正已载)——
- **芯片定案(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 07 补充。
不配合场景兜底 = 芯片 TIMEOUT/KeepAlive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-07 补充。
### 5.2 relisten 沉降段统一(延后项,登记不做强行收口)
`net_relisten_locked` 隔离段仍是**手拼**序列(排空 RX + 主动 close + 读本 Socket SINT + 轮询状态 + 30ms 重试轮),
未统一改走 `sock_reset` 原语。判定:它**不是忙等**(每轮短 SPI + 交重试轮询,netTask 无长阻塞),
且 Trap 23 的沉降等级(GRACEFUL 100ms / QUARANTINE 400ms / 800ms 强制放行 < 2s 时限)是**生产已调优**的时序,
统一改造的回归面覆盖每次断连热路径,收益只是"少一段重复代码"——按 Trap 25 纪律**延后**,
且 TRAP-NET-18 的沉降等级(GRACEFUL 100ms / QUARANTINE 400ms / 800ms 强制放行 < 2s 时限)是**生产已调优**的时序,
统一改造的回归面覆盖每次断连热路径,收益只是"少一段重复代码"——按 TRAP-NET-20 纪律**延后**,
待下一次触碰该热路径的任务顺路处理(`sock_reset` 原语已具备承接条件:二段 CLOSE、quiescent 排空、等级化最短等待)。
## 6. 横向纪律
+37 -23
View File
@@ -19,7 +19,7 @@
### E1 · 压力下对账/pump/D10 命中量化(台架,不烧录,用现网固件)
- **假设**:常态下 `reconcile` 命中≈0(E0 已证 3.5h 零命中);快速复连压力下命中才出现,
且 B3 修复后"自家制造的丢中断"占比应显著低于 Trap 22 时代。
且 B3 修复后"自家制造的丢中断"占比应显著低于 TRAP-NET-17 时代。
- **方法**(设备跑当前产品固件,PC 侧压测):
1. `n30_repro.py --n 50 --size 1024`(:21 背靠背 STOR);
2. `ftp_regression.py`(多数据连接会话);
@@ -28,7 +28,7 @@
4. `iec104_master.py` 连接/断开 ×10。
每轮之间 serial `clear`,跑完 grep `reconcile|pump:|relisten|give up|lost` 计数 + 恢复延迟。
- **判据**:
- reconcile 命中数 / 50 轮(目标基线:Trap 22 时代 2/10 死亡 → 现在应 0 死亡);
- reconcile 命中数 / 50 轮(目标基线:TRAP-NET-17 时代 2/10 死亡 → 现在应 0 死亡);
- `pump: chip not listening` 与 `relisten retry ok` 次数与间隔(量化 D10 恢复窗);
- 端口拒绝窗(connect 被拒的间隔分布)→ 决定阶段 3 后"恢复窗 ~1s 拒绝新连接"是否仍需改善。
- **门禁**:阶段 3 单一 reconciler 的周期/去抖参数;F-NET-7 收窄优先级。
@@ -70,8 +70,8 @@
### E4 · sock_reset 内部顺序 A/B(裸机芯片台架 + 生产路径复现)
- **假设**:Trap 23 排渣只需"排空 RX(读空)→ close → 排空事件 → 等 CLOSED",
`CLEAR_RECV_BUF` 命令可不用(Trap 25 已证缓冲重分配无效);GRACEFUL 与 QUARANTINE 差异仅在
- **假设**: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` 变体
@@ -112,7 +112,7 @@ E6 挂机期间并行实施阶段 0。
| 模式 | 命中 | 说明 |
|---|---|---|
| `reconcile` | **0** | Trap 22 对账整窗口零命中(冷路径坐实) |
| `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)各若干次会话收尾 |
@@ -147,7 +147,7 @@ bit3=1 契约)→ 释放路径**只置事实不重建**(N-32 定案)→
`net_listener_try` 失败 → 30ms 后 D10 重试成功(~75ms)。**每次会话收尾产生 ~0.3–1s 监听空窗**;
空窗内 SYN 被静默丢弃(无 RST),客户端 1s RTO 重传 → :7003 复连 p50≈1020ms。
→ 直接支撑"重建内联进释放事务"(mbed/FreeRTOS reuse 模型)与 `sock_reset` 原语;
也解释 Trap 22 时代"恢复窗拒绝新连接"的口径。
也解释 TRAP-NET-17 时代"恢复窗拒绝新连接"的口径。
**发现 B(P0,新):背靠背数据连接失效循环,现固件可复现 9/10**:
失败签名(sock4=FTP 数据监听):数据连接 accept 后 **~20ms** 被**上一条连接的陈旧 DISCONNECT**
@@ -156,7 +156,7 @@ bit3=1 契约)→ 释放路径**只置事实不重建**(N-32 定案)→
立即 relisten → 下一条连接仍落在残渣投递窗内 → 再被杀——**自持循环**。`closed-with-data accept`
偶发救回(窗口内仅 2 次)但 lftpd 数据门控(`lftpd_inet_accept_timeout` 直读 `p_sock->state` 镜像)
错过窗口 → 5s 超时 → 425。
对照台账:Trap 25 定案回归 = `ftp_regression`(有间隔)11/11,**n30_repro(背靠背)不在终版回归集**;
对照台账: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)是残渣投递的供给面。
@@ -206,7 +206,7 @@ CONNECT/RECV_OK(net_poll 门控焊死)→ 数据连接事件永不到达 lft
| ftp_regression | 11/11 |
| :7003 100ms×50 | 50/50 p50=1011ms |
| iec104_master | 18/18 PASS |
| Trap 23 RST×5 / FIN×5(`MDK-ARM/ftp_abort_probe.py --check-abort`) | 5/5(450)/ 5/5(226) |
| 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 域)——**自愈型、无簇、无数据损坏**。
@@ -218,7 +218,7 @@ CONNECT/RECV_OK(net_poll 门控焊死)→ 数据连接事件永不到达 lft
### E8 残余偶发深度排查(2026-10-13 00:06–00:2x,提交 9ac9879 后固件,台架锁 N34)
**背景**:E7b 报"残余 ~3%"后,Trap 23 abort 探针(RST×5+FIN×5)再跑 n30 → **27/50**
**背景**:E7b 报"残余 ~3%"后,TRAP-NET-18 abort 探针(RST×5+FIN×5)再跑 n30 → **27/50**
(两个持续簇 #6-25、#44-50)——"3%"不是稳定值,故障率随设备状态在 2%~54% 间摆动。
**实验矩阵(同固件、同负载,仅设备状态不同)**:
@@ -245,7 +245,7 @@ pump → settle ×4 → auto-relisten ok ← 800ms 强制放行才出簇
**机制链(三个叠加缺陷,登记 F-NET-17)**:
1. **沉降自再生锁存**:沉降段 `net_listener.c:174` 的 `close_socket` 在芯片已 CLOSED
后再 close 仍会(经拆链收尾)重新锁存 DISCONNECT;`:176` 的 SINT 读清发生在
close 后**同一轮**,而锁存在 close 后 ~10-20ms 才落(Trap 14 实证)→ 沉降最后一轮
close 后**同一轮**,而锁存在 close 后 ~10-20ms 才落(TRAP-NET-12 实证)→ 沉降最后一轮
读清后残留一枚"迟到锁存",relisten 完成后 11ms 杀死首条连接。30ms 重试粒度 vs
10-20ms 锁存延迟 = 每次重建掷一次硬币(E7b 的 ~2% 偶发即此)。
2. **泵与救回互抢**:沉降段 `:172` `net_drain_chip_rx` 排空的正是 closed-with-data
@@ -494,7 +494,7 @@ TIMEOUT → 即 E8 记录的**残渣风暴态**,且**跨烧录存活**(重
1. 现状驱动**确属规格偏离**(零 TSL 间隔,按"间隔"语义解读),但**未测得实际危害**:
460 轮/≈58MB 双向字节级校验仅 1 次未复现异常,且两臂无差异。
2. 依 Trap 25 铁律「不留未证明的复杂度」:**当前不建议改产品路径**,而是
2. 依 TRAP-NET-20 铁律「不留未证明的复杂度」:**当前不建议改产品路径**,而是
**登记该偏离 + 本轮证据**;B 变体作为**待用补丁**保留(若将来出现芯片批次
差异、提高 SCK、或不明数据错位,可直接采用——它已证明无吞吐/正确性回退)。
3. **方法学教训(E13 修正版,后续时序/连接类实验必读)**:本台架的**僵尸会话
@@ -599,14 +599,14 @@ CLOSE_SOCKET 是二段关闭**。CLOSE 命令对 LISTEN 态 Socket 返回 SUCCES
但只拆监听(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 09 open 重试
(3 次 × 500ms 沉降)第二次 open 前的 close 恰好充当二段——这就是 Trap 09
超时后照常放行,随后重 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 09 式重试,不得把 DONE_CLOSE 当单次 CLOSE 的充分条件。
要么保留 TRAP-NET-09 式重试,不得把 DONE_CLOSE 当单次 CLOSE 的充分条件。
### E3 结果(同场同固件,5 命令 × 5 轮 × 2 遍 = 50 样本,两遍签名逐条一致)
@@ -625,7 +625,7 @@ TC1502b 实证 LISTEN 态复位 GRACEFUL ~180ms 落定。
**三条实证**:
1. **残留假阳性坐实**:陈旧错误码 0x17(ERR_VAL,源自"对已关闭 Socket 再 close")
跨后续 SET 命令(不更新状态)持续存活——复核 `docs/问题记录/CH395F_Trap_Records.md`
Trap 25 定案轮的"陈旧残留"形态(RESET_ALL 陈旧 BUSY 变体本矩阵未复现,同类
TRAP-NET-20 定案轮的"陈旧残留"形态(RESET_ALL 陈旧 BUSY 变体本矩阵未复现,同类
陈旧值持续存活已证)。
2. **OPEN/LISTEN/CLOSE 均在 <2ms 完成**,BUSY 从未观察到——"BUSY→终态"转变对这三条
命令不可观测;驱动 `poll_cmd_status` 20ms 粒度对它们恒白等 ≥20ms(无害但无判别价值)。
@@ -643,26 +643,26 @@ GET_CMD_STATUS 仅保留两用途:INIT_CH395 类慢命令(内核前/测试
### E4 结果(同场同固件 + 生产路径基线;3 变体 ×10 轮 ×2 遍 = 60 轮)
**方法**:每轮先造污染会话(主机 `test/host/ch395f_reuse_host.py --mode e4`:连上 →
发 3000B → RST,Trap 23 中止形态),芯片侧按变体执行复位序列,**立即**原端口重开
发 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 23 机制直接证据) |
| 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 14
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 25"缓冲重分配无效"指的是 set_send/recv_buf 重分配,不是 CLEAR)。
等价——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 会
@@ -670,7 +670,7 @@ C 无)清的是粘滞 SINT 位——对**事件驱动 accept 的正确性**必
byte0 已是 00H)——"等 CLOSED"几乎零成本,但为契约必需步。
**生产路径基线**(烧 bench 固件前,Phase 1.5 产品固件 Code=209780):
`MDK-ARM/ftp_abort_probe.py --check-abort --rounds 5` → **5/5**(中止 STOR→450;
`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 序列一致。
@@ -687,7 +687,7 @@ C 不等价(0/20 + 级联)→ **`ch395f_sock_reset` 内部序列定案**:
GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排 RX + 最短等待"中
"是否排 RX"一半被否(带脏缓冲关闭在任何释放形态下都危险),两模式差异只剩
最短等待(100ms/400ms,Trap 23 二阶段实测值)。
最短等待(100ms/400ms,TRAP-NET-18 二阶段实测值)。
### E-02 收口台架(2026-10-13,阶段 3 产品固件 Code=211348,`test/host/e02_probe.py`)
@@ -700,7 +700,7 @@ GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排
| 阶段 | 观察 |
|---|---|
| 预算耗尽 | `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 07 定案更正),监听恢复**受限于对端 close**;复位机在 `net_poll` 步进里等待,不搁浅 |
| 对端不配合(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) |
@@ -718,4 +718,18 @@ GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排
**结论**: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 07 补充。
芯片侧物理边界(无 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 零成本),需要时重新武装。
@@ -18,7 +18,7 @@
## 1. 七个误会(叠加才成为"玄学")
### ① 门铃会丢(Trap 22:丢中断)
### ① 门铃会丢(TRAP-NET-17:丢中断)
快速复连时芯片本应"按门铃"(INT# 拉低)通知有新来访/客人离开,但门铃**会哑**——
人已经站在屋里,铃没响。传达员只靠门铃干活,于是本层状态**静默滞留**:端口其实
@@ -26,7 +26,7 @@
死掉"就是它——不是断网,是**没人报信**。裸机芯片台架(剥离 OS/net/FTP)独立复现,
证明这是芯片天性,不是固件 bug(见 `docs/网络/N34-自愈审计/README.md` 结论 1)。
### ② 门铃总闸是"看一眼就清零"(Trap 25 B3)
### ② 门铃总闸是"看一眼就清零"(TRAP-NET-20 B3)
全局中断寄存器是**一整排灯,读一次全部熄灭**。历史上 open/relisten 里有两处
"丢弃式读一遍"——等于**查自己门铃时把全楼别的住户的门铃线也拔了**,别人的
@@ -98,14 +98,14 @@ sequenceDiagram
| 环 | 拆法(已落地) | 证据/落点 |
|---|---|---|
| ① 丢中断 | 单一 reconciler:100 ms 巡逻对账五种形态,判据读驱动状态缓存 | Trap 22;实验方案 E2(回读 18.1 µs 背书);实施方案 阶段 3 项 1 |
| ② 总闸读清 | 全局寄存器唯一读者=net_poll(F-NET-7);open/relisten 只清本 Socket SINT | Trap 25 B3;实施方案 阶段 0/2 |
| ① 丢中断 | 单一 reconciler:100 ms 巡逻对账五种形态,判据读驱动状态缓存 | TRAP-NET-17;实验方案 E2(回读 18.1 µs 背书);实施方案 阶段 3 项 1 |
| ② 总闸读清 | 全局寄存器唯一读者=net_poll(F-NET-7);open/relisten 只清本 Socket SINT | TRAP-NET-20 B3;实施方案 阶段 0/2 |
| ③ 拆柜台 | 铁律"EOF 不得 close 监听 fd" + 新 API `net_drop_conn`(拆会话保监听) | 生命周期设计说明 §10.10;实验方案 E-02 |
| ④ 多所有者 | 重建收敛唯一所有者(net_listener + reconciler),其余只置事实不动手 | README 结论 4(不变量 II);实施方案 阶段 3 项 1 |
| ⑤ 队头阻塞 | 忙等统一 `net_sock_reset_sync` 有界排渣(600 ms 硬顶);recv 非阻塞消息化 | 实验方案 E-02(banner 39 ms);阶段 1.5 |
| ⑥ 僵尸会话 | keepalive 60/5/3 + 芯片 TIMEOUT → QUARANTINE → 重建(30~90 s 自愈) | 实验方案 E11/E13(用户判断"不该依赖冷启动"被证实) |
| ⑦ 无 RST | 定案入 Trap 07:兜底靠查岗不靠 RST;`net_drop_conn` 本地拆除 + 对账③④ 认领 | Trap 07 定案更正;实验方案 E-02 收口 |
| ⑧ 残渣风暴 | `sock_reset` 标准排渣(读空→CLOSE→排空事件→byte0=00H 确认)+ M3 矛盾态门 | Trap 23/25;实施方案 M3/M5(E8/E9 风暴态免疫) |
| ⑦ 无 RST | 定案入 TRAP-NET-07:兜底靠查岗不靠 RST;`net_drop_conn` 本地拆除 + 对账③④ 认领 | TRAP-NET-07 定案更正;实验方案 E-02 收口 |
| ⑧ 残渣风暴 | `sock_reset` 标准排渣(读空→CLOSE→排空事件→byte0=00H 确认)+ M3 矛盾态门 | TRAP-NET-18/-20;实施方案 M3/M5(E8/E9 风暴态免疫) |
## 5. 一句话总结
@@ -43,7 +43,7 @@ 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 22 对账 + 监听重建唯一入口,N34 阶段 3 起原 `net_listener_pump` 已并入)→ 排空消息队列 |
| **中栏 · 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 看门狗降级) |
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
@@ -198,7 +198,7 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
| # | 门禁 | 结果 |
|---|---|---|
| 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 = 既有芯片侧问题(Trap 21,2026-09-23 结案非缺陷) |
| 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 半登录态即其场景) |
+170 -270
View File
@@ -1,273 +1,213 @@
# CH395F 驱动陷阱记录
# CH395F 驱动陷阱记录(TRAP-NET 系列)
记录开发过程中遇到的所有坑、根因、解决方案,避免重复踩坑。
CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修复、回归判据。改驱动/NET 层前先读本文。
> **2026-10-13 重组审计**:合并同根因条目(14+17、13+21)、删除已被推翻的误判叙述(只留一行误判存档)、
> 按主题分章并加索引;条目格式统一为 `## Trap NN · 短标题` + 固定小节(状态/现象/根因(定案)/修复/回归判据/教训,
> 视情况取舍)。**编号永不重排**——代码注释大量引用 `Trap NN`,合并条目标题保留双编号;
> Trap 19 从未存在(悬空引用登记在第六章待修清单)。
> 状态图例:✅ 已修复定案 · ⏳ 待修 · ↩️ 已撤回(非缺陷) · ↦ 已移入他档 · — 从未存在。
> 编号口径:按 `docs/编号规范.md` 采用 `TRAP-NET-<nn>`,连续 01~21。编号沿革(2026-10-13 全库重排):
> 旧 01~11 不变;旧 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` 为违规裸形已全库迁移;
> 旧号只存在于历史提交与旧日志。编号不复用、不重排。
> 状态图例:✅ 已修复定案 · ⏳ 待修。
## 索引
| 编号 | 标题 | 状态 | 一句话结论 |
|---|---|---|---|
| 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 短名 | ↦ | **非 CH395F 议题**(FTP 改造期归档错位)→ 已移入 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 14 项 |
| 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-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 | 快速复连丢中断 | ✅ | 芯片丢 CONNECT/DISCONNECT → 周期对账 `net_reconcile_tick`(reconciler 之源) |
| 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-21 | IP 显示段序与对端地址 | ✅ | `net_inet_ntoa` 段序 MSB 首;对端地址用 `net_getpeername`,勿用 `getsockname` |
---
# 第一章 初始化与配置
## Trap 01 · Socket 配置顺序:先数据 Socket 后监听 Socket
## TRAP-NET-01 · Socket 配置顺序:先数据 Socket 后监听 Socket
- **状态**:✅ 已定案(多连接模式虽已随 `319d00e3` 删除,**顺序规则仍适用于现行 `net_init`**——`net_socket.c` 注释「Trap 01:先配数据 Socket 1~7,再配监听 Socket 0」)
- **现象**:多连接模式下最多接受 3 个客户端,Socket 4~7 的 CONNECT 中断从不触发,SYN 被静默丢弃。
- **状态**:✅(顺序规则适用于现行 `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 但保留配置,可再分配。
- **发现时间**:Phase 8 压力测试(原记 2025-07,早于仓库首提交 `b1d05091` 2026-06-05,日期不可考)。
## Trap 02 · Socket 缓冲区必须显式分配且不重叠
## TRAP-NET-02 · Socket 缓冲区必须显式分配且不重叠
- **状态**:✅ 已定案(现行 `net_init` 固定布局 8×(4收+2发)=48 块即用满)
- **状态**:✅(现行 `net_init` 固定布局 8×(4收+2发)=48 块即用满)
- **现象**:多 Socket 数据互相覆盖、收到错误数据或客户端挂死;未分配缓冲的 Socket 发送为固定垃圾内容(`0x0028` 填充)、接收存不进数据、CONNECT/RECV 中断不触发。
- **根因(定案)**:芯片默认只为 Socket 0~3 分配独立收发缓冲(48 块×512B 用满,`docs/芯片手册/CH395F.md` §5.45),Socket 4~7 为零块;未显式分配或误用相同块号导致重叠。
- **修复**:每个数据 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,不可考)。
- **修复**:每个 Socket 独占块(现行固定 6 块/Socket=4收+2发,与 MSS 解耦)。**必须在 `open_socket` 之前**调 `ch395f_set_send_buf/set_recv_buf`。
- **教训**:缓冲几何以 §5.45 的 48×512B 为准("32×1KB"说法有误,实测块 0~47 全部有效)。
## Trap 05 · CH395F 与 RTL8305NBI 自动协商不兼容
## TRAP-NET-05 · CH395F 与 RTL8305NBI 自动协商不兼容
- **状态**:✅ 已修复
- **状态**:✅
- **现象**:`ch395f_get_phy_status()` 恒返回 `PHY_DISCONN`,链路无法建立。
- **根因(定案)**:CH395F PHY 与 RTL8305NBI-CG 直连时自动协商失败(板级组合)。
- **修复**:初始化协议栈后强制 `ch395f_set_phy(CH395F_PHY_100M_FULL)`。
- **发现时间**:项目初期硬件联调。
## Trap 06 · KeepAlive 参数必须为 500ms 倍数
## TRAP-NET-06 · KeepAlive 参数必须为 500ms 倍数
- **状态**:✅ 已定案(产品参数 60s/5s/3 的转正过程见附录 A「KeepAlive 定案链」)
- **状态**:✅(产品参数 60s/5s/3 的定案链见附录 A)
- **现象**:设置 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
## TRAP-NET-15 · FUN_PARA bit0 迷思:bit0 保留必须为 0
- **状态**:✅ 已定案(`net_init` 用 `ch395f_set_fun_para(0x08)`)
- **误判存档**:本节曾断言"bit0=单/多 Socket 模式,必须 0x09 才能用 Socket 1~7"——**无任何出处,已撤回**;当年让 Socket 1~7 真正可用的是缓冲显式分配(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。
- **状态**:✅(`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 24 · SET_MAC 必须在 INIT_CH395 之前
## 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 之后则本次上电不生效。原码顺序相反;`ch395f.c` 原注释把四个 SET 标为"芯片已初始化(之后)"加剧误排。
- **修复**:`net_init()` 把 `ch395f_set_mac_addr()` 移到 `ch395f_init()` 之前;四个 SET 函数注释统一改"必须在 `ch395f_init()` 之前调用";`net_socket.h` 同步。
- **发现时间**:2026-10-04(静态审查项 P1-7)。
- **状态**:✅(纯顺序调整,无 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 08 · RECV 中断是电平触发
## TRAP-NET-08 · RECV 中断是电平触发
- **状态**:✅ 已定案
- **状态**:✅
- **现象**:`net_poll()` 中 RECV 中断无限触发,主循环永远得不到执行。
- **根因(定案)**:接收缓冲区有数据期间 `INT#` 保持低电平,`do{...}while(int_status!=0)` 必然死循环。
- **修复**:`net_poll()` 每轮只处理一批中断,由主循环负责读数据。
- **推论**:RECV 是条件位(数据读空即落),所以 Trap 25 B3 的全量读清对 RECV 可下轮补、对其余位是永久销毁。
- **推论**:RECV 是条件位(数据读空即落),所以 B3 的全量读清对 RECV 可下轮补、对其余位是永久销毁。
## Trap 14/17 · 上一条连接的残留 DISCONNECT 污染下一条连接(合并条目)
## TRAP-NET-12 · 上一条连接的残留 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 实证)→ 无人消费则跨会话存活。
- **状态**:✅(两级预清 + 排空为 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 丢弃陈旧事件(`phase2_tc203()` 双保险;生产 `net_connect_locked` open 后清本 Socket 中断);
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 矛盾时以状态为准)。
- **措辞更正(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 取证。
- **注意**:B3 修复后预清只准读**本 Socket** SINT,不得再全量读 8 个(见第六章 B3)。
## Trap 22 · 快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡
## TRAP-NET-17 · 快速复连丢 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_reconcile_tick` 是 N34 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。
- **修复(`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(确定性复现+当日修复闭环)。
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 25 · FTP 数据连接三连故障——四重定案(N-30)
## TRAP-NET-20 · FTP 数据连接三连故障——三重定案
- **状态**:✅ 已定案并修复(回归 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(对账不是元凶)。
- **有效修复一:双信源原则——中断只是快路径,状态寄存器才是真相**:
- **状态**:✅(回归 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。
- **已证伪的修复**(受控实验全部无效,列出防重踩):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**(生产 LIST#4"accept 成功但数据被吞"的直接成因就是对账②曾把幽灵补 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 复用无关)。
- **定案二:所谓"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)。
- **定案二: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 07 · TCP 关闭重连:直接 close,不要 disconnect
## TRAP-NET-07 · TCP 关闭重连:直接 close,不要 disconnect
- **状态**:✅ 已定案(做法保留;原根因说法已推翻,见定案更正)
- **误判存档**:原断言"`disconnect → close` 让 Socket 卡 `FIN_WAIT_2` 数分钟、close 发 RST"——**TC-NET-204 实测推翻**(2026-10-11),机制说法作废,做法保留。
- **定案(TC-NET-204,三模式各 5 轮 45 项判定全过)**:
- **状态**:✅ 定案(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)。
| 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 拆僵尸会话(判死窗 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 到达即自动走完——**不搁浅**。
## Trap 09 · 沉降窗:PHY link up 后 / close 后不能立即 open
## TRAP-NET-09 · 沉降窗:PHY link up 后 / close 后不能立即 open
- **状态**:✅ 已定案(现行形态=open 重试环 + `net_sock_reset_sync`;原 `osDelay(7000)` 启动延时已被就绪屏障取代)
- **现象**:PHY_CHANGE 中断同一时刻 `ch395f_open_socket()` 返错(非 BUSY 超时),首次 listen 失败;3 秒后重试成功。
- **状态**:✅(现行形态=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 ≪ 原 500ms 盲等;
3. ~~FTP 任务 `osDelay(7000)` 错峰~~ → 已由**就绪屏障**(`SYS_EVT_NET_READY`/`FTP_READY`)取代(2026-10 起)。
- **补充(2026-10-13,E2/E4 台架):LISTEN 态 Socket 的二段关闭**——本条"close 后需沉降才能重开"的前置形态:
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 必成"。
- 驱动侧已内置补发:`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
## TRAP-NET-11 · net_accept_locked 拒绝 standalone_accept 的 ESTABLISHED
- **状态**:✅ 已修复定案(现行 `net_accept_locked` 仍保留该放行逻辑)
- **状态**:✅(现行 `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)。
- **修复**:放行 `standalone_accept && state == ESTABLISHED` 组合。仅影响 standalone_accept 模式(PASV 数据通道、单连接 FTP)。
## Trap 23 · 上传中止后首笔复连上传被静默截断(≤2048B)并误报成功
## TRAP-NET-18 · 上传中止后首笔复连上传被静默截断(≤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`)不受影响(不按传输复用)。
- **状态**:✅(三阶段全部闭环;回归判据常备:`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 稳定通过,对 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)。
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(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。
- **回归基线**:`--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 04 · UDP 发送后必须等待 SENDBUF_FREE
## TRAP-NET-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 归零 / 接收缓冲冻结」家族——非缺陷(合并结案)
## TRAP-NET-14 · NET 层目的 IP 字节序写反 → CONNECT TIMEOUT
- **状态**:↩️ 已撤回(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 输入输出命令字节顺序均为低字节在前"与官方例程自相矛盾,**本仓以实测+例程为准**。
- **根因(定案)**:`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()` 同样修正;③ 诊断回读按小端语义重组再打印。
- **字节序总口径(务必分清两套)**:
@@ -275,143 +215,103 @@
- **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 · 单次写超过发送缓冲区(写穿)
## TRAP-NET-16 · 单次写超过发送缓冲区(写穿)
- **状态**:✅ 已修复定案(措辞已按 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 直接**写穿发送缓冲**(超出部分覆盖/丢失或破坏指针)。
- **根因(定案)**:分块上限误取 `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`,分块由驱动保证。
- **发现时间**:阶段 8 大文件传输实现,2026-08-26。
## TRAP-NET-21 · IP 显示段序与对端地址(getsockname ≠ 对端)
- **状态**:✅(N34 阶段 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 03 · DHCP 包 xid 偏移错误(主机工具)
## 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 模式。
- **发现时间**:阶段 4(原记 Phase 6 2025-07 有误)。
## Trap 10 · 消息队列值拷贝导致结果无法回传
## TRAP-NET-10 · 消息队列值拷贝导致结果无法回传
- **状态**:✅ 已定案(现行契约:`net_*` 错误码走通知值——AGENTS 模块约定)
- **状态**:✅(现行契约:`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)。
- **注意**:指针成员(`msg.buf`)不受值拷贝影响;内核级 `_locked` API 直接操作不受影响。
## Trap 12 · FatFS 路径与 8.3 短名(↦ 已移入他档)
## TRAP-NET-13 · DHCP 成功后 IP 寄存器提交滞后
- **状态**:↦ 移档——**非 CH395F 议题**(FTP 改造期归档错位;`docs/网络/N34-自愈审计/CH395F驱动层审计.md` 早已点破"Trap 12 非 CH395F")。完整内容与根因更正(含按 `Lib/FatFs/ff.c` 删去的两条误判根因)已移入 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 14 项(2026-10-13)。
- 编号在此保留仅为旧引用不断链;发现期:FTP 改造期(2026-08)。
## Trap 15 · DHCP 成功后 IP 寄存器提交滞后
- **状态**:✅ 已定案(测试侧稳定轮询+交叉比对)
- **现象**:`GET_DHCP_STATUS` 已置 0x00 后立即 `GET_IP_INF` 读到的仍是旧静态配置,而芯片实际已按新租约收发——三方数据矛盾构成假阳性(2026-08-24 上板实测)。
- **状态**:✅(测试侧稳定轮询+交叉比对)
- **现象**:`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。
- **发现时间**:2026-08-24。
---
# 第六章 2026-10-11 寄存器/数值口径全量审计(A/B 系列)
# 第六章 审计遗留与待修(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),无新增同类 |
## 新发现(去重后按严重度)
2026-10-11 寄存器/数值口径全量审计(四路并行:驱动逐命令 + NET/测试逐调用点 + Trap 复核;三源交叉:`docs/芯片手册/CH395F.md` + WCH 官方例程 + git 历史)的未闭环项。A 系列与已修 B 项的结论已沉淀进上文条目,不再重复;B 编号冻结不重排。
| # | 严重度 | 位置 | 问题 | 状态 |
|---|---|---|---|---|
| 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 TC701~703 绑本地口)会被误判孤儿 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 个重建所有者与 U1~U15 无人负责组合、芯片原生多连接模式(A′)为何对本产品不可用) |
| 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 一并处置 |
| 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` |
## 台架注入陷阱(方法论,2026-10-12 起持续补充)
## 台架注入陷阱(方法论,持续补充)
- **Windows 出站防火墙规则不能模拟"对端静默死亡"**:WFP 对**已建立**连接有状态缓存,中途 `New-NetFirewallRule -Direction Outbound -Action Block` 不影响既有流——实测静置+阻断 250s 芯片毫无反应,看似"KeepAlive 不生效",实则 ACK 一直正常往返。
- **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 用禁用网卡的近似注入完成定案。
- **主机侧回显服务器收不到入站连接 → 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 }`。
- **主机侧回显服务器收不到入站连接 → 全量 `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 干净启动,并取多样本再比均值。
- **对照组必须从干净态起测**:上一腿若**没死透**就重启下一腿,对照组失真。每个样本前 `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 生效",把相关性当因果。
- **判读通则(与 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`)与撤回机理无关,**勿回退**。
## 主机侧测试工具过期(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_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`)。
## 文档侧待修清单(其余登记在此)
## 文档侧待修清单
- **最高优先级**: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。
- ~~已撤回机理仍被当作现行约束~~(**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` 宏已不存在)。
- 悬空引用:`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 移入存储架构说明 §10.1——**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`)。
- 悬空引用:`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 · 驱动关键点速查(细节以对应 Trap 为准)
# 附录 A · 驱动关键点速查(细节以对应条目为准)
- **初始化顺序**:`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 0~3;Socket 4~7 必须 `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 抓包确认,超时恢复) |
- **初始化顺序**:`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` E12)。
- **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` 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 链)。
- **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)。