docs(net): N34 E11/E12 实验记录与台架脚本(KeepAlive 服务器侧缺口、TSL 规格偏离)

E11:服务器侧 KeepAlive 未开启(静态证据链 + 注入手段两次修正 + 实测
生产参数 30.0s 判死、空闲存活会话不误杀),给出采纳建议;防火墙规则
对已建立连接无效、有效注入 = 禁用 PC 网卡,两条方法学留痕。
E12:缓存区读写路径长度→数据零间隔属规格偏离(读 0.4us/写 0.2us),
但 460 轮/约 58MB 字节级校验未测得可复现危害;依 Trap 25 登记偏离、
B 变体(两段+TSL,+28B,吞吐无回退)留作待用补丁。
残渣风暴态跨烧录存活污染 A/B 连接级对比,教训写入 E12:实验前后必须
冷启动(断电重上电),MCU 覆写烧录/RESET_ALL 不足以清除。
新增 test/host/e11_keepalive_probe.py(idle/block 两模式 + 注入正对照)
与 test/host/ftp_integrity_test.py(STOR→RETR 全量 SHA-256 比对,
建议纳入常备回归集)。
This commit is contained in:
2026-10-13 09:10:17 +08:00
parent eac36526e7
commit d96113bcc4
4 changed files with 418 additions and 0 deletions
@@ -388,3 +388,108 @@ Phase13 宏全量重编 **0 错 0 警**(Code=216632)后还原宏再重编产
227,824→228,172 B、Flash 合计 228,928→229,276 B;**RAM 两区未变**
(RW_IRAM1 97,864 B/74.7%、RW_CCM 57,376 B/87.9%),印证阶段 0/1 未动 RAM。
此后本分支常规提交不再需要 `--no-verify`。
### E11 结果(2026-10-13 07:01–07:18,CH395F 服务器侧 KeepAlive,临时实验固件)
**问题**:`CMD_SET_KEEPALIVE_SN`(手册 §5.56)——芯片在"IDLE 无收发 → 探测
CNT 次无 ACK"后产生 SINT_STAT_TIM_OUT / DISCONNECT。怀疑本仓没有利用它。
**静态证据(怀疑成立)**:
| 事实 | 证据 |
|---|---|
| 参数配了:idle 60s / intvl 5s / cnt 3(均 500 倍数、IDLE>INTVL) | `net_config.h:61-70`,`net_socket.c:298-302`(INIT_CH395 之前) |
| 开关**全仓仅一处**,且在 TCP **客户端**分支 `CONNECTING` | `net_socket.c:3257` |
| `App/` 目录**零个 `net_connect()`** → 生产无任何 socket 会走到该行 | grep 全仓 |
| 手册明确:TCP **服务器**应在 `SINT_STAT_CONNECT` 后开启 | `docs/芯片手册/CH395F.md` §5.56(:815-821) |
| 服务器侧调用 2026-10-12 曾加后**撤回**,理由仅覆盖"物理链路失效";注释自认"链路在、对端静默"未覆盖且**无法注入** | `net_socket.c:3223-3229` |
| 唯一能发现该缺口的 Phase 11 测试**默认关闭** | `test/board/ch395f_test.h`(`//#define ENABLE_PHASE11_TESTS`) |
**注入手段的两次修正(方法学留痕)**:
1. **Windows 防火墙入站 DROP **无效****:连接建立后再加规则,对该已建立连接
不生效(ALE 层在连接建立时判定)。正对照实测:装规则后 KEEPLIVE 回显**照常
收到** → `VERDICT: injection INEFFECTIVE`(`e11_inject_check.py`)。这也解释了
首轮 235s 窗口无 TIMEOUT(对端一直在 ACK,并非 keepalive 失效)。
2. **有效注入 = 禁用 PC 网卡**:中间有交换机(判别依据:ESTABLISHED 会话下
禁用 PC 网卡 12s,设备**零网络事件** → 设备侧 PHY 链路保持)。此时对端不可达、
设备链路在、且不产生 FIN/RST —— 正是"链路在、对端静默"。
**实测(实验固件:`handle_connect_event` LISTENING 分支加
`ch395f_set_keepalive_enable(sock,1)` + 调用点 DBG 留痕;TCP :7003)**:
| 试验 | 配置/注入 | 结果 |
|---|---|---|
| T2 正常空闲存活 | 生产参数 60/5/3,对端存活但静默 90s | KEEPLIVE 回显正常、**无 TIMEOUT** → 不误杀空闲会话 ✓ |
| T4 定标(短参数) | 6s/2s/2,禁用 PC 网卡 40s | 留痕 `keepalive enabled (idle=6000 intvl=2000 cnt=2)`;**T+5.1s** 报 `[ERR] sock3 TIMEOUT` → 参数按配置缩放,芯片默认值(20/15/9)假设**被否** ✓ |
| T5 生产参数 | 60/5/3,禁用 PC 网卡 90s | 留痕 `idle=60000 intvl=5000 cnt=3`;**T+30.0s** 报 `[ERR] sock3 TIMEOUT` → QUARANTINE → relisten → **新连接成功恢复** ✓ |
**结论**:
- KeepAlive 能力**真实可用**,且本层**已有现成消费者**(`net_poll` 派发
TIM_OUT → `handle_timeout_event` QUARANTINE → pump 重建)——打开开关即可闭环。
- 生产参数下实测判死 **≈30.0s**(早于手册公式 75s;芯片定时语义与手册公式
不一致,**以实测值为准**)。短参数实测 ≈5.1s,证明参数生效且可按需调优。
- 静默但**存活**的空闲会话不被误杀(T2)——因为对端会 ACK 探测包。
- 2026-10-12 撤回的盲区(链路在、对端静默)**正是本机制覆盖的场景**;当年
"+2s 即报 TIM_OUT"只覆盖了物理断链。
- **建议采纳**:在 `handle_connect_event` 的 LISTENING 分支加回一行
`ch395f_set_keepalive_enable(sock, 1)`(手册规定位置),并重新打开 Phase 11
测试纳入回归;参数是否调优(如缩短 IDLE)由实测判死窗口需求决定。
- ⚠ 台架副作用:本轮反复实验后设备落入 E8 残渣风暴态(详见 E12),需**冷启动
(断电重上电)**恢复,MCU 复位/RESET_ALL 不足以清除。
### E12 结果(2026-10-13 07:18–08:05,SPI 缓存区 TSL 时序 A/B,临时实验固件)
**问题**:手册 §7.4——SPI/并口模式下读写收发缓存区命令**只需**在"长度与数据
之间"留 TSL(读 0.4us / 写 0.2us),其余字节间无需间隔;**非缓存区命令**另有
TSC=0.6us(命令→数据)、TSD=0.3us(数据字节间)。本仓是否满足?
**静态证据(没考虑过,且缓存区路径零间隔)**:
| 事实 | 证据 |
|---|---|
| 全仓无任何 TSL 相关代码/注释(grep 只命中手册) | — |
| 两个缓存区函数把 `[cmd, sock, lenL, lenH, data...]` 打包进**同一 DMA 缓冲、一次连续突发**(CS 全程拉低)→ 长度→数据**间隔 = 0** | `ch395f.c` `write_send_buf` / `read_recv_buf`(原实现) |
| 其余命令走逐字节 `HAL_SPI_TransmitReceive(...,1,...)` → TSC/TSD 有 µs 级余量 ✓ | 同上 |
| 量化:SPI2 分频 8 @APB1 42MHz = **5.25MHz**(单字节 1.52us);TSL 0.4us ≈ 2.1 个 SCK 周期 | `Src/spi.c:84` |
**A/B 设计**:A = 现状(零间隔单突发);B = 头/数据两段传输 + 段间显式忙等
(`CH395F_TSL_LOOPS=40` ≈1.0–1.4us,另叠加两段间 HAL/DMA 重配开销;CS 全程拉低,
帧形与手册图一致)。B 代价:代码 +28B(Code 209580→209608)、写路径多一次
memcpy;**吞吐无回退**(A 40×128KB 51.8s vs B 47.9s)。
**字节级校验(`test/host/ftp_integrity_test.py`:STOR→RETR 全量 SHA-256 比对,
差异自动二次回读归因)**:
| 臂 | 样本 | 结果 |
|---|---|---|
| A(零间隔) | 260 轮(20×64KB + 240×128KB ≈32MB/方向) | **1 次不一致**(A2 轮,细节未留档,A3/A4/A5 未复现) |
| B(两段+TSL) | 200 轮(40+100+60 ×128KB ≈25.6MB/方向) | **0 次不一致** |
→ **未观察到 TSL 造成的可复现数据错位**;两臂均接近"零错位"。
**连接级 n30 ×50(每次运行前重烧录 = MCU 复位 + RESET_ALL)**:
| 臂 | 各轮失败数 | 有失败的轮次 |
|---|---|---|
| A | 8, 22, 0, 0, 12, 0, 0, 39 | 4/8 |
| B | 0, 0, 0, 0, 0, 47 | 1/6 |
**污染控制与关键反证**:序列末恢复产品镜像后再跑 n30 ×20 = **16/20 失败**,
日志签名 = PASV 数据连接被芯片接受后立即关闭(`sock4 st=0x00/0x00`)+ 控制连接
TIMEOUT → 即 E8 记录的**残渣风暴态**,且**跨烧录存活**(重烧 + @test PASS 后依旧)
→ 与 E8"普通复位后仍 12%"一致。A 39/50 与 B 47/50 **两臂都出现**灾难轮
→ 该差异**不能归因于 TSL**,E12 的连接级对比**被此状态噪声吞没,无结论**。
**结论(诚实口径)**:
1. 现状驱动**确属规格偏离**(零 TSL 间隔,按"间隔"语义解读),但**未测得实际危害**:
460 轮/≈58MB 双向字节级校验仅 1 次未复现异常,且两臂无差异。
2. 依 Trap 25 铁律「不留未证明的复杂度」:**当前不建议改产品路径**,而是
**登记该偏离 + 本轮证据**;B 变体作为**待用补丁**保留(若将来出现芯片批次
差异、提高 SCK、或不明数据错位,可直接采用——它已证明无吞吐/正确性回退)。
3. **方法学教训(后续 SPI 时序实验必读)**:本台架的**芯片残渣风暴态**必须在
实验前后受控(冷启动断电重上电,或专用的"残渣清理协议");**MCU 覆写烧录
(复位 + RESET_ALL)不足以清除**——实测同一镜像在重烧后仍可 0/50..47/50 摆动,
跨臂污染使 A/B 失效。E8 起就存在的这条口径,本轮再次被独立复现。
+18
View File
@@ -214,12 +214,30 @@ CH395F 的 RECV 中断是电平触发的——只要接收缓冲区有数据就
### SPI 通信
- 每次 SPI 事务需调用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹
- **大数据量收发使用 SPI2 DMA**:`ch395f_write_send_buf()` 和 `ch395f_read_recv_buf()` 已改造为 DMA 批量传输(DMA1_Stream3 RX, DMA1_Stream4 TX),命令和配置操作仍使用逐字节轮询
- ⚠ **TSL 规格偏离(N34 E12,2026-10-13 登记)**:手册 §7.4 要求缓存区命令在
"长度与数据之间"留 TSL(读 0.4us / 写 0.2us;其余字节间无需间隔;非缓存区命令
另有 TSC=0.6us / TSD=0.3us)。本仓两个缓存区函数把命令头与数据打包进**同一
DMA 突发**(CS 全程拉低)→ 该间隔为 **0**,属规格偏离;其余命令逐字节传输,
TSC/TSD 有大余量。SPI2 = 5.25MHz(分频 8 @42MHz,单字节 1.52us)。
**实测未发现危害**:460 轮 / ≈58MB 双向 SHA-256 全量比对仅 1 次未复现异常,
且"两段+TSL"变体(+28B,吞吐无回退)与现状无差异 → 依 Trap 25 铁律暂不改产品
路径,偏离与证据登记于此、变体留作待用补丁。完整 A/B 与方法学见
`docs/网络/N34-自愈审计/实验方案.md` E12。
- DMA 缓冲区:`s_spi2_dma_tx_buf/rx_buf[CH395F_SPI_DMA_BUF_SIZE]` = 接收缓冲 2048 + 4 = **2052**(`ch395f.h:148`),4 字节对齐;单次有效载荷上限 2048(历史值 1500 / 4100 均已作废,见 Trap 13 复核注)
### TCP 参数配置(必须在 INIT_CH395 之前)
- **重传参数**:`SET_RETRAN_COUNT`(默认12次,最大20)和 `SET_RETRAN_PERIOD`(默认500ms,最大1000ms),总重传时间 = 次数 × 周期
- **KeepAlive 参数必须为 500ms 的倍数**:`IDLE`(默认20000ms)、`INTVL`(默认15000ms),且 IDLE > INTVL;传入非 500 倍数的值导致 CH395F 内部定时器异常
- **KeepAlive 默认关闭**:需在 `SINT_STAT_CONNECT` 后调用 `ch395f_set_keepalive_enable(sock, 1)` 启用
- ⚠ **本仓现状与 E11 实测(2026-10-13)**:`ch395f_set_keepalive_enable` 全仓仅
客户端分支(`CONNECTING`)调用,而 `App/` 无任何 `net_connect()` → **产品上
KeepAlive 实际全程关闭**;服务器侧调用 2026-10-12 曾加后撤回(理由仅覆盖
物理链路失效)。E11 用"禁用 PC 网卡"注入"链路在、对端静默"(防火墙规则对
已建立连接无效,见实验方案 E11 方法学),实测服务器侧开启后:生产参数
60s/5s/3 → **判死 ≈30.0s**(早于手册公式 75s,以实测为准);短参数 6s/2s/2
→ ≈5.1s(证明参数生效);静默但存活的对端**不被误杀**(90s 空闲 KEEPLIVE
回显正常)。消费链路现成:TIM_OUT → `handle_timeout_event` QUARANTINE →
pump relisten → 端口恢复。完整记录见 `docs/网络/N34-自愈审计/实验方案.md` E11。
### Socket 4~7 缓冲区
- CH395F 默认只给 Socket 0~3 分配独立收发缓冲(共 48 块 × 512B 用满),Socket 4~7 为零块