docs(N34): E2/E3/E4 实验结果与定案 + bench 实验区复活
E2:状态回读 18.1us(N=1000x2 逐值一致);LISTEN 可见延迟 100% <=10ms 无长尾(24 轮)-> "每 poll 全量刷新"成立、reconciler 去抖 可减为 1。 E3:GET_CMD_STATUS 对 OPEN/LISTEN/CLOSE 无判别力(残留假阳性), 定案 cmd_done 用状态回读判据;SET 类命令态永不更新。 E4:排渣序列定案=读空 RX -> CLOSE -> 排空事件(quiescent) -> 轮询 byte0=00H;C 变体(不排渣)0/20 且级联自持,Trap 23 残渣 恰等于单 Socket RX 缓冲整容(rxdrain=2048,19/20)。 E2 补充发现(Phase 15 板测):LISTEN 态 CLOSE_SOCKET 二段关闭 (一次只拆监听 byte0 恒 05,再发一次才落 00)——Trap 09 open 重试环的芯片级原因。 bench:ch395f_reuse_bench.c 复活 N30_CHIP_BENCH 门控实验区 (旧 N-30 矩阵 s_bench_legacy=0 停泊),ch395f_reuse_host.py 增 --mode e4。注册(uvprojx/main.c)实验完已还原不入库。
This commit is contained in:
@@ -562,3 +562,129 @@ PASV 重监听被垂死僵尸 accept 的竞态——且可由网卡抖动**确
|
||||
|
||||
**工具与留痕**:`test/host/pasv_probe.py`(新增);pktmon 抓包留存
|
||||
`%TEMP%\n30_cap.etl`(3.9MB,含网卡抖动窗口,设备 IP 过滤)。
|
||||
|
||||
### E2 结果(2026-10-13 11:43–11:50,bench 实验固件 Code=213412,跑两遍,台架锁 N34)
|
||||
|
||||
**固件**:复活 `test/board/ch395f_reuse_bench.c`(N30_CHIP_BENCH 门控,内核前裸机;
|
||||
旧 N-30 定案矩阵由 `s_bench_legacy=0` 停泊保留);注册 uvprojx Test 组 + `Src/main.c`
|
||||
USER CODE 2 区(注册不入库,实验完 `git restore`)。DWT CYCCNT @168MHz(1 cycle≈5.95ns)。
|
||||
两遍独立运行,本节数字两遍一致(第二遍为第一遍 C 变体级联污染后的干净重跑,E2/E3 复验)。
|
||||
|
||||
**单次回读成本**(`ch395f_get_socket_status`,N=1000×2 遍,两遍逐值相同):
|
||||
|
||||
| 统计 | cycles | µs |
|
||||
|---|---|---|
|
||||
| min | 3048 | 18.1 |
|
||||
| avg | 3057 | 18.1 |
|
||||
| max | 3481 | 20.7 |
|
||||
|
||||
**LISTEN 可见性矩阵**(裸 TCP_LISTEN 不轮询命令态,发命令后 +0/10/50/100ms 回读;
|
||||
100ms 仍不可见则 10ms 节奏续查到 1000ms;12 轮×2 遍=24,轮间换端口):
|
||||
|
||||
| 首次观察到 TCP=01H | 计数 |
|
||||
|---|---|
|
||||
| +0ms | 14 |
|
||||
| +10ms | 10 |
|
||||
| >10ms | **0** |
|
||||
|
||||
**判据结论**:
|
||||
1. 单次回读 18.1µs ≪ 100µs → **"每 poll 全量刷新"成立**(4 监听 × ~18µs ≈ 72µs ≪
|
||||
1ms 轮询周期;`docs/网络/N34-自愈审计/实施方案.md` §4 第 1 项预估 ~15µs 量级坐实)。
|
||||
2. LISTEN 可见延迟有界:100% 在 +10ms 内可见、无长尾 → **reconciler 去抖可减为 1 次**
|
||||
(决策点后 ≥10ms 单次回读即可裁定 LISTEN 形态;现"连续 2 次"去抖对该形态无必要——
|
||||
瞬态窗口仅存在于 0~10ms 内,且第二次回读必在窗口外)。
|
||||
|
||||
**E2 补充发现(2026-10-13 12:23,Phase 15 板测 TC1501/1502b 实证):LISTEN 态
|
||||
CLOSE_SOCKET 是二段关闭**。CLOSE 命令对 LISTEN 态 Socket 返回 SUCCESS(0x00),
|
||||
但只拆监听(TCP 01H→00H),**byte0 恒留 05H(Socket 仍 OPEN),2s 内不自落**;
|
||||
需再发一次 CLOSE(此时 TCP 已 00H)byte0 才落 00H(二段间隔实测 0ms 即落)。
|
||||
生产 net 层早已在**无意中吸收**该语义:`net_close_locked` 等 CLOSED 的 500ms 窗
|
||||
超时后照常放行,随后重 listen 时 `net_listen_locked` 的 Trap 09 open 重试
|
||||
(3 次 × 500ms 沉降)第二次 open 前的 close 恰好充当二段——这就是 Trap 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 的充分条件。
|
||||
|
||||
### E3 结果(同场同固件,5 命令 × 5 轮 × 2 遍 = 50 样本,两遍签名逐条一致)
|
||||
|
||||
**方法**:发命令前对照读 GET_CMD_STATUS(记录残留假阳性);发命令后按手册
|
||||
`docs/芯片手册/CH395F.md` §9.2.1 节奏(≥2ms 首查)以 ~1ms 轮询至终态(2s 上限)。
|
||||
`pre`=对照读值,`first`=首个非 BUSY 终值,`busy`=是否观察到 BUSY→终态转变。
|
||||
|
||||
| 命令 | pre(对照读) | busy | first | done | 判别力判定 |
|
||||
|---|---|---|---|---|---|
|
||||
| OPEN_SOCKET | 0x00(r0) / **0x17**(r1-4) | 否(0/10) | 0x00 | 3ms | **部分**:状态确被 OPEN 更新(0x17→0x00),但 pre=SUCCESS 时完成与残留不可分 |
|
||||
| TCP_LISTEN | 0x00(残留 SUCCESS) | 否(0/10) | 0x00 | 3ms | **不成立**:无转变,残留 SUCCESS 不可甄别 |
|
||||
| CLOSE_SOCKET | 0x00(残留 SUCCESS) | 否(0/10) | 0x00 | 3ms | **不成立**:同上 |
|
||||
| SET_SOUR_PORT | **0x17** | 否 | **0x17(不变)** | — | **不成立**:SET 类根本不更新命令状态 |
|
||||
| SET_PROTO_TYPE | **0x17** | 否 | **0x17(不变)** | — | **不成立**:同上 |
|
||||
|
||||
**三条实证**:
|
||||
1. **残留假阳性坐实**:陈旧错误码 0x17(ERR_VAL,源自"对已关闭 Socket 再 close")
|
||||
跨后续 SET 命令(不更新状态)持续存活——复核 `docs/问题记录/CH395F_Trap_Records.md`
|
||||
Trap 25 定案轮的"陈旧残留"形态(RESET_ALL 陈旧 BUSY 变体本矩阵未复现,同类
|
||||
陈旧值持续存活已证)。
|
||||
2. **OPEN/LISTEN/CLOSE 均在 <2ms 完成**,BUSY 从未观察到——"BUSY→终态"转变对这三条
|
||||
命令不可观测;驱动 `poll_cmd_status` 20ms 粒度对它们恒白等 ≥20ms(无害但无判别价值)。
|
||||
3. **SET 类无判别力**(命令状态完全不被更新)——Trap 记录结论复核成立。
|
||||
|
||||
**判据结论(定案 `docs/网络/N34-自愈审计/实施方案.md` §4 第 4 项)**:五条命令均不满足
|
||||
"发后 ≤N ms 变终态 **且** 无残留假阳性" → **`ch395f_cmd_done` 完成判据改用状态回读**:
|
||||
open 完成=byte0 变 05H;listen 完成=TCP=01H;close 完成=byte0=00H
|
||||
(回读成本 18.1µs 由 E2 实测背书;listen 可见延迟 ≤10ms 由 E2 矩阵背书,成本与时效均成立)。
|
||||
GET_CMD_STATUS 仅保留两用途:INIT_CH395 类慢命令(内核前/测试路径)与**命令级被拒**
|
||||
检测(非 SUCCESS 非 BUSY 错误码仍会被芯片写入命令状态,B2 判据仍有效——被拒码是
|
||||
"新值覆盖残留"方向,不受残留歧义影响)。`poll_cmd_status` 保留给内核前/测试路径,
|
||||
产品 socket 路径弃用。
|
||||
|
||||
### E4 结果(同场同固件 + 生产路径基线;3 变体 ×10 轮 ×2 遍 = 60 轮)
|
||||
|
||||
**方法**:每轮先造污染会话(主机 `test/host/ch395f_reuse_host.py --mode e4`:连上 →
|
||||
发 3000B → RST,Trap 23 中止形态),芯片侧按变体执行复位序列,**立即**原端口重开
|
||||
+ 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 机制直接证据) |
|
||||
| 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
|
||||
"粘滞到被读",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)。
|
||||
带非空 RX 关闭违反 `docs/芯片手册/CH395F.md` §5.52 bit3=1"读出残留数据后再关闭"
|
||||
契约 → 芯片拆链停滞 → 下一会话 SYN 被吞/CONNECT 丢失,且自持。排空事件(A/B 有、
|
||||
C 无)清的是粘滞 SINT 位——对**事件驱动 accept 的正确性**必需(0x04 陈旧 RECV 会
|
||||
误导消费者),但不是 TCP 层吞 SYN 的根因。所有轮 closed_ms=0(close 命令落定后
|
||||
byte0 已是 00H)——"等 CLOSED"几乎零成本,但为契约必需步。
|
||||
|
||||
**生产路径基线**(烧 bench 固件前,Phase 1.5 产品固件 Code=209780):
|
||||
`MDK-ARM/ftp_abort_probe.py --check-abort --rounds 5` → **5/5**(中止 STOR→450;
|
||||
设备日志 `sock4 TIMEOUT` → quarantine → `UPLOAD '/ab000N.bin' FAILED` →
|
||||
followup `UPLOAD '/ab_ok.bin' 4096 bytes OK`)——现实现(隔离期持续排空 RX +
|
||||
close + 排空事件 + 等 CLOSED)行为基线全绿,与变体 A 序列一致。
|
||||
|
||||
**判据结论(定案 `docs/网络/N34-自愈审计/实施方案.md` §4 第 3 项)**:
|
||||
(a) 与现实现等价成立(A 20/20 载荷完整);(b) B 与 A 等价(无差异记录);
|
||||
C 不等价(0/20 + 级联)→ **`ch395f_sock_reset` 内部序列定案**:
|
||||
|
||||
1. 读空 RX(GET_RECV_LEN→READ_RECV_BUF 直到 0;CLEAR_RECV_BUF 等价可用,**取读空**
|
||||
——保 §5.52 契约且排空字节数可观测可留痕);
|
||||
2. CLOSE_SOCKET;
|
||||
3. 排空本 socket 事件(drain-until-quiescent,E8 M5 口径:连续两轮无新位);
|
||||
4. 轮询 byte0=00H(非阻塞状态机步进,约束不变)。
|
||||
|
||||
GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排 RX + 最短等待"中
|
||||
"是否排 RX"一半被否(带脏缓冲关闭在任何释放形态下都危险),两模式差异只剩
|
||||
最短等待(100ms/400ms,Trap 23 二阶段实测值)。
|
||||
|
||||
@@ -10,7 +10,16 @@
|
||||
* 运行方法:在实验分支(如 n30-chip-bench)把本 .c 注册进 uvprojx Test 组、
|
||||
* Src/main.c 的 USER CODE 2 区在 app_main_init() 之前调 ch395f_reuse_bench()
|
||||
* (内核前裸机循环,永不返回,断电退出),烧录后跑主机脚本对照芯片日志。
|
||||
* 当前矩阵为定案轮(对照 vs "丢 CONNECT 捕获自 accept"修复组),见 s_vars[]。
|
||||
* 旧 N-30 定案矩阵(对照 vs "丢 CONNECT 捕获自 accept"修复组)保留在 s_vars[]/
|
||||
* bench_round(),由 s_bench_legacy=0 停泊不运行。
|
||||
*
|
||||
* 2026-10-13 N34 阶段 2 前置实验(task-9):同一固件合并跑 E2/E3/E4——
|
||||
* E2 = GET_SOCKET_STATUS 单次 SPI 成本(DWT 周期计数)+ TCP_LISTEN 可见延迟矩阵;
|
||||
* E3 = GET_CMD_STATUS 判别力矩阵(OPEN/LISTEN/CLOSE/SET_SOUR_PORT/SET_PROTO_TYPE,
|
||||
* 含"命令未发时"对照读,复核 Trap 记录的 SET 类无判别力 + 陈旧 BUSY 残留);
|
||||
* E4 = sock_reset 内部顺序 A/B/C 变体 ×10 轮(轮间换端口)+ 立即复连载荷完整性自证
|
||||
* (配套 test/host/ch395f_reuse_host.py --mode e4)。
|
||||
* 判据与结果 → docs/网络/N34-自愈审计/实验方案.md E2/E3/E4 条目与实验记录节。
|
||||
*
|
||||
* close_mode:0=主机先关(芯片等 DISCONNECT);1=芯片先关;2=无数据连上即关
|
||||
* verify:0=无修复;1=listen 后确认 LISTEN 重试;2=缓冲重分配;
|
||||
@@ -23,6 +32,7 @@
|
||||
|
||||
#include "ch395f.h"
|
||||
#include "dbg_log.h"
|
||||
#include "main.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
|
||||
/* 变体表:{sock, close 后复开延迟 ms, TX 字节数, close_mode, verify_listen} */
|
||||
@@ -89,9 +99,10 @@ static int bench_chip_init(void) {
|
||||
return -1;
|
||||
}
|
||||
ch395f_reset();
|
||||
/* 等 PHY link(Trap 09:link up 前 open 必失败) */
|
||||
/* 等 PHY link(Trap 09:link up 前 open 必失败;B9 修正:判据是 != DISCONN,
|
||||
* 旧写法 !=0 对 01H=DISCONN 也成立) */
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (ch395f_get_phy_status() != 0U) {
|
||||
if (ch395f_get_phy_status() != CH395F_PHY_DISCONN) {
|
||||
break;
|
||||
}
|
||||
HAL_Delay(100);
|
||||
@@ -354,20 +365,460 @@ static void bench_round(unsigned idx) {
|
||||
HAL_Delay(v->delay_ms);
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* N34 阶段 2 前置实验区(2026-10-13):E2 / E3 / E4 一次固件合并跑
|
||||
* ===================================================================== */
|
||||
|
||||
/* 旧 N-30 定案矩阵停泊开关:0 = 不运行(保留代码备查,勿删) */
|
||||
static const int s_bench_legacy = 0;
|
||||
|
||||
/* --- DWT 周期计数器:168 MHz,1 cycle ~= 5.95 ns(不依赖 HAL 时基/中断) --- */
|
||||
static void bench_dwt_enable(void) {
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CYCCNT = 0U;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
}
|
||||
|
||||
static uint32_t bench_cyc(void) {
|
||||
return DWT->CYCCNT;
|
||||
}
|
||||
|
||||
/* cycles -> us(1 位小数,纯整数运算:MicroLIB 不支持 %f) */
|
||||
static void bench_log_cyc(const char *tag, uint32_t cyc) {
|
||||
DBG_INFO("BENCH %s=%lu.%luus (%lucyc)", tag,
|
||||
(unsigned long)(cyc / 168U),
|
||||
(unsigned long)(((cyc % 168U) * 10U) / 168U),
|
||||
(unsigned long)cyc);
|
||||
}
|
||||
|
||||
/* --- 裸命令下发:绕过驱动 poll_cmd_status(E2/E3 要观察命令自身状态轨迹)。
|
||||
* CS 包裹与驱动内部 ch395f_spi_begin/end 完全同构(该二函数为 static 未导出) --- */
|
||||
static void bench_spi_begin(void) {
|
||||
HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET);
|
||||
HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
static void bench_spi_end(void) {
|
||||
HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET);
|
||||
}
|
||||
|
||||
static void bench_raw_cmd(uint8_t cmd, uint8_t sock, const uint8_t *args, uint8_t nargs) {
|
||||
bench_spi_begin();
|
||||
(void)ch395f_write_cmd(cmd);
|
||||
(void)ch395f_write_data(sock);
|
||||
for (uint8_t i = 0U; i < nargs; i++) {
|
||||
(void)ch395f_write_data(args[i]);
|
||||
}
|
||||
bench_spi_end();
|
||||
}
|
||||
|
||||
/* 等 Socket byte0 回 00H(SOCKET_CLOSED);返回等待耗时 ms,-1=超时 */
|
||||
static int bench_wait_closed(uint8_t sock, uint32_t timeout_ms) {
|
||||
uint32_t t0 = HAL_GetTick();
|
||||
uint8_t st[2] = { 0, 0 };
|
||||
for (;;) {
|
||||
ch395f_get_socket_status(sock, st);
|
||||
if (st[0] == CH395F_SOCKET_CLOSED) {
|
||||
return (int)(HAL_GetTick() - t0);
|
||||
}
|
||||
if ((uint32_t)(HAL_GetTick() - t0) >= timeout_ms) {
|
||||
return -1;
|
||||
}
|
||||
HAL_Delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 排空本 Socket 事件:全局位图门控 + SINT 读(读即清),连续 2 轮(20ms 节奏)无新位即停;
|
||||
* 返回排空期间见过的 SINT 位 OR(E8 M5 drain-until-quiescent 的 bench 版) */
|
||||
static uint8_t bench_drain_events(uint8_t sock, uint32_t max_ms) {
|
||||
uint32_t t0 = HAL_GetTick();
|
||||
uint8_t seen = 0U;
|
||||
int quiet = 0;
|
||||
while ((uint32_t)(HAL_GetTick() - t0) < max_ms) {
|
||||
uint16_t glob = ch395f_get_glob_int_status_all();
|
||||
uint8_t si = 0U;
|
||||
if ((glob & (1U << (sock + 4U))) != 0U) {
|
||||
si = ch395f_get_sock_int_status(sock);
|
||||
}
|
||||
if (si != 0U) {
|
||||
seen |= si;
|
||||
quiet = 0;
|
||||
} else if (++quiet >= 2) {
|
||||
break;
|
||||
}
|
||||
HAL_Delay(20);
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
/* 读空 RX(GET_RECV_LEN -> read_recv_buf 直到长度 0),返回总字节数 */
|
||||
static uint32_t bench_drain_rx(uint8_t sock) {
|
||||
uint32_t total = 0U;
|
||||
for (;;) {
|
||||
uint16_t rl = ch395f_get_recv_len(sock);
|
||||
if (rl == 0U) {
|
||||
break;
|
||||
}
|
||||
uint16_t n = (rl > 2048U) ? 2048U : rl;
|
||||
ch395f_read_recv_buf(sock, s_rx_buf, n);
|
||||
total += n;
|
||||
if (total > 65535U) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/* 让命令落定:>=2ms 节奏轮询 GET_CMD_STATUS 直到非 BUSY;返回终值 */
|
||||
static uint8_t bench_cmd_settle(uint32_t max_ms) {
|
||||
uint32_t t0 = HAL_GetTick();
|
||||
uint8_t s;
|
||||
do {
|
||||
HAL_Delay(2);
|
||||
s = ch395f_get_cmd_status();
|
||||
} while (s == CH395F_ERR_BUSY && (uint32_t)(HAL_GetTick() - t0) < max_ms);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* ------------------------- E2:状态回读成本 + LISTEN 可见延迟 ------------------------- */
|
||||
static void bench_e2(void) {
|
||||
static const uint16_t grid_ms[4] = { 0U, 10U, 50U, 100U };
|
||||
uint8_t st[2] = { 0, 0 };
|
||||
uint32_t min = 0xFFFFFFFFUL, max = 0U, sum = 0U;
|
||||
|
||||
DBG_INFO("BENCH E2 ===== GET_SOCKET_STATUS cost + LISTEN visibility =====");
|
||||
/* 1) 单次回读成本 N=1000(socket 空闲态,与生产 net_poll 刷新同形) */
|
||||
for (uint32_t i = 0U; i < 1000U; i++) {
|
||||
uint32_t c0 = bench_cyc();
|
||||
ch395f_get_socket_status(3U, st);
|
||||
uint32_t d = bench_cyc() - c0;
|
||||
if (d < min) {
|
||||
min = d;
|
||||
}
|
||||
if (d > max) {
|
||||
max = d;
|
||||
}
|
||||
sum += d;
|
||||
}
|
||||
bench_log_cyc("E2 gs_min", min);
|
||||
bench_log_cyc("E2 gs_avg", sum / 1000U);
|
||||
bench_log_cyc("E2 gs_max", max);
|
||||
|
||||
/* 2) LISTEN 可见性矩阵:裸 TCP_LISTEN(不 poll 命令态)后 +0/10/50/100ms 回读;
|
||||
* 100ms 仍不可见则 10ms 节奏续查到 1000ms,记录首次观察到 TCP=01H 的延迟 */
|
||||
for (unsigned r = 0U; r < 12U; r++) {
|
||||
uint16_t port = (uint16_t)(5200U + r);
|
||||
int grid[4] = { 0, 0, 0, 0 };
|
||||
int first_ms = -1;
|
||||
(void)ch395f_close_socket(3U);
|
||||
(void)bench_wait_closed(3U, 2000U);
|
||||
ch395f_set_proto_type(3U, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port(3U, port);
|
||||
if (ch395f_open_socket(3U) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E2 v2 r%u open fail", r);
|
||||
continue;
|
||||
}
|
||||
uint32_t t0 = HAL_GetTick();
|
||||
bench_raw_cmd(CH395F_CMD_TCP_LISTEN_SN, 3U, 0, 0U);
|
||||
for (int g = 0; g < 4; g++) {
|
||||
while ((uint32_t)(HAL_GetTick() - t0) < grid_ms[g]) { }
|
||||
ch395f_get_socket_status(3U, st);
|
||||
if (st[1] == CH395F_TCP_LISTEN) {
|
||||
grid[g] = 1;
|
||||
if (first_ms < 0) {
|
||||
first_ms = (int)(HAL_GetTick() - t0);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (first_ms < 0 && (uint32_t)(HAL_GetTick() - t0) < 1000U) {
|
||||
HAL_Delay(10);
|
||||
ch395f_get_socket_status(3U, st);
|
||||
if (st[1] == CH395F_TCP_LISTEN) {
|
||||
first_ms = (int)(HAL_GetTick() - t0);
|
||||
}
|
||||
}
|
||||
DBG_INFO("BENCH E2 listen r%u port%u grid=%d%d%d%d first=%dms st=0x%02X/0x%02X",
|
||||
r, (unsigned)port, grid[0], grid[1], grid[2], grid[3],
|
||||
first_ms, st[0], st[1]);
|
||||
(void)bench_cmd_settle(2000U);
|
||||
(void)ch395f_close_socket(3U);
|
||||
(void)bench_wait_closed(3U, 2000U);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------- E3:GET_CMD_STATUS 判别力矩阵 ------------------------- */
|
||||
static void bench_e3_one(const char *name, uint8_t cmd, uint8_t sock,
|
||||
uint16_t port0, int pre_open, int pre_listen,
|
||||
const uint8_t *args, uint8_t nargs) {
|
||||
for (int r = 0; r < 5; r++) {
|
||||
uint16_t port = (uint16_t)(port0 + (uint16_t)r);
|
||||
uint8_t largs[2];
|
||||
const uint8_t *pa = args;
|
||||
uint8_t n = nargs;
|
||||
(void)ch395f_close_socket(sock);
|
||||
(void)bench_wait_closed(sock, 2000U);
|
||||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port(sock, port);
|
||||
if (pre_open) {
|
||||
if (ch395f_open_socket(sock) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E3 %s r%d pre-open fail", name, r);
|
||||
continue;
|
||||
}
|
||||
if (pre_listen) {
|
||||
if (ch395f_tcp_listen(sock) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E3 %s r%d pre-listen fail", name, r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 对照读:命令未发时的残留状态(Trap 记录:陈旧 BUSY 残留即在此现形) */
|
||||
uint8_t pre = ch395f_get_cmd_status();
|
||||
if (cmd == CH395F_CMD_SET_SOUR_PORT_SN) {
|
||||
largs[0] = (uint8_t)(port & 0xFFU);
|
||||
largs[1] = (uint8_t)(port >> 8);
|
||||
pa = largs;
|
||||
n = 2U;
|
||||
}
|
||||
uint32_t t0 = HAL_GetTick();
|
||||
bench_raw_cmd(cmd, sock, pa, n);
|
||||
int busy_seen = 0, polls = 0, done_ms = -1;
|
||||
uint8_t firstv = 0xFFU;
|
||||
HAL_Delay(2); /* 手册 §9.2.1:发命令后 >=2ms 才发首个 GET_CMD_STATUS */
|
||||
while ((uint32_t)(HAL_GetTick() - t0) < 2000U) {
|
||||
uint8_t s = ch395f_get_cmd_status();
|
||||
polls++;
|
||||
if (s == CH395F_ERR_BUSY) {
|
||||
busy_seen = 1;
|
||||
HAL_Delay(1);
|
||||
continue;
|
||||
}
|
||||
firstv = s;
|
||||
done_ms = (int)(HAL_GetTick() - t0);
|
||||
break;
|
||||
}
|
||||
DBG_INFO("BENCH E3 %s r%d pre=0x%02X busy=%d first=0x%02X done=%dms polls=%d",
|
||||
name, r, pre, busy_seen, firstv, done_ms, polls);
|
||||
(void)bench_cmd_settle(1000U);
|
||||
(void)ch395f_close_socket(sock);
|
||||
(void)bench_wait_closed(sock, 2000U);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_e3(void) {
|
||||
static const uint8_t proto_tcp = CH395F_PROTO_TYPE_TCP;
|
||||
DBG_INFO("BENCH E3 ===== GET_CMD_STATUS discriminative matrix =====");
|
||||
bench_e3_one("OPEN", CH395F_CMD_OPEN_SOCKET_SN, 3U, 5300U, 0, 0, 0, 0U);
|
||||
bench_e3_one("LISTEN", CH395F_CMD_TCP_LISTEN_SN, 3U, 5310U, 1, 0, 0, 0U);
|
||||
bench_e3_one("CLOSE", CH395F_CMD_CLOSE_SOCKET_SN, 3U, 5320U, 1, 1, 0, 0U);
|
||||
bench_e3_one("SET_SOUR_PORT", CH395F_CMD_SET_SOUR_PORT_SN, 3U, 5330U, 0, 0, 0, 0U);
|
||||
bench_e3_one("SET_PROTO_TYPE", CH395F_CMD_SET_PROTO_TYPE_SN, 3U, 5340U, 0, 0, &proto_tcp, 1U);
|
||||
}
|
||||
|
||||
/* ------------------------- E4:sock_reset 内部顺序 A/B/C -------------------------
|
||||
* A:读空 RX -> CLOSE -> 排空本 socket 事件 -> 轮询等 byte0=00H
|
||||
* B:CLEAR_RECV_BUF -> CLOSE -> 排空事件 -> 等 CLOSED
|
||||
* C:什么都不做 -> CLOSE -> 等 CLOSED
|
||||
* 每轮先"污染会话"(主机连上发 3000B 后 RST,Trap 23 形态),复位后立即
|
||||
* open+listen,主机立即上传 4096B 图案载荷;芯片侧按图案自证完整性
|
||||
* (PASS 口径 = 消费者在 DISCONNECT/TIM_OUT 前实际读到的字节数与图案)。 */
|
||||
#define E4_SOCK 3U
|
||||
#define E4_BASE_PORT 5100U
|
||||
#define E4_ROUNDS 10U
|
||||
#define E4_PAYLOAD 4096U
|
||||
static uint8_t s_e4_acc[8192];
|
||||
|
||||
static uint8_t e4_seed(unsigned rg) {
|
||||
return (uint8_t)(rg * 97U + 13U);
|
||||
}
|
||||
|
||||
/* 污染阶段收尾:等芯片 DISCONNECT/TIM_OUT(RST 中止的死亡事件),1=见到 */
|
||||
static int bench_e4_wait_death(uint8_t sock, uint32_t timeout_ms, uint8_t *out_bits) {
|
||||
uint32_t t0 = HAL_GetTick();
|
||||
while ((uint32_t)(HAL_GetTick() - t0) < timeout_ms) {
|
||||
uint16_t glob = ch395f_get_glob_int_status_all();
|
||||
if ((glob & (1U << (sock + 4U))) != 0U) {
|
||||
uint8_t si = ch395f_get_sock_int_status(sock);
|
||||
if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) {
|
||||
if (out_bits != 0) {
|
||||
*out_bits = si;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
HAL_Delay(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bench_e4_round(unsigned rg, uint8_t variant) {
|
||||
uint16_t port = (uint16_t)(E4_BASE_PORT + rg);
|
||||
uint8_t si = 0U;
|
||||
uint32_t rx_drained = 0U;
|
||||
int closed_ms;
|
||||
|
||||
DBG_INFO("BENCH E4 rg%u var=%c port%u t=%u", rg, (char)('A' + variant),
|
||||
(unsigned)port, (unsigned)HAL_GetTick());
|
||||
/* 轮前统一清洁(所有变体同起点) */
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
(void)bench_wait_closed(E4_SOCK, 2000U);
|
||||
(void)bench_drain_events(E4_SOCK, 300U);
|
||||
ch395f_set_proto_type(E4_SOCK, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port(E4_SOCK, port);
|
||||
if (ch395f_open_socket(E4_SOCK) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E4 rg%u open fail", rg);
|
||||
return;
|
||||
}
|
||||
if (ch395f_tcp_listen(E4_SOCK) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E4 rg%u listen fail", rg);
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
return;
|
||||
}
|
||||
/* 污染会话:主机 connect -> 发 3000B -> RST;芯片侧只等死亡事件(不读 RX,
|
||||
* 残渣留给变体处置——A 的排空 / B 的 CLEAR / C 的什么都不做 正是被测对象) */
|
||||
if (!bench_e4_wait_death(E4_SOCK, 15000U, &si)) {
|
||||
DBG_ERROR("BENCH E4 rg%u contam: no death event (host not synced?)", rg);
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
(void)bench_wait_closed(E4_SOCK, 2000U);
|
||||
HAL_Delay(500);
|
||||
return;
|
||||
}
|
||||
/* 被测复位序列 */
|
||||
if (variant == 0U) {
|
||||
rx_drained = bench_drain_rx(E4_SOCK);
|
||||
} else if (variant == 1U) {
|
||||
ch395f_clear_recv_buf(E4_SOCK);
|
||||
}
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
if (variant != 2U) {
|
||||
(void)bench_drain_events(E4_SOCK, 500U);
|
||||
}
|
||||
closed_ms = bench_wait_closed(E4_SOCK, 2000U);
|
||||
DBG_INFO("BENCH E4 rg%u var=%c reset done rxdrain=%lu closed_ms=%d t=%u",
|
||||
rg, (char)('A' + variant), (unsigned long)rx_drained, closed_ms,
|
||||
(unsigned)HAL_GetTick());
|
||||
/* 立即重开 + LISTEN(主机立即上传全载荷) */
|
||||
ch395f_set_sour_port(E4_SOCK, port);
|
||||
if (ch395f_open_socket(E4_SOCK) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E4 rg%u reopen fail", rg);
|
||||
return;
|
||||
}
|
||||
if (ch395f_tcp_listen(E4_SOCK) != CH395F_ERR_SUCCESS) {
|
||||
DBG_ERROR("BENCH E4 rg%u relisten fail", rg);
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
return;
|
||||
}
|
||||
/* 等载荷连接 CONNECT(C 变体的陈旧 DISCONNECT 若在此同读出现 = 新连接被杀,如实计 FAIL) */
|
||||
if (!bench_wait_sock_int(E4_SOCK, 15000U, &si) || ((si & CH395F_SINT_STAT_CONNECT) == 0U)) {
|
||||
DBG_ERROR("BENCH E4 rg%u var=%c VERIFY FAIL: no CONNECT (si=0x%02X)", rg,
|
||||
(char)('A' + variant), si);
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
(void)bench_wait_closed(E4_SOCK, 2000U);
|
||||
(void)bench_drain_events(E4_SOCK, 300U);
|
||||
HAL_Delay(200);
|
||||
return;
|
||||
}
|
||||
/* 消费循环:电平式 GET_RECV_LEN 收集(对丢 RECV 免疫),DISCONNECT/TIM_OUT 即止
|
||||
* (= 生产 net 层释放语义;closed-with-data 救回不在本判据内,事后读空仅作取证) */
|
||||
int death = ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) ? 1 : 0;
|
||||
uint32_t total = 0U;
|
||||
uint32_t tr = HAL_GetTick();
|
||||
while (!death && (uint32_t)(HAL_GetTick() - tr) < 6000U) {
|
||||
uint16_t rl = ch395f_get_recv_len(E4_SOCK);
|
||||
while (rl > 0U && total < (uint32_t)sizeof(s_e4_acc)) {
|
||||
uint16_t n = (rl > 2048U) ? 2048U : rl;
|
||||
uint32_t room = (uint32_t)sizeof(s_e4_acc) - total;
|
||||
if ((uint32_t)n > room) {
|
||||
n = (uint16_t)room;
|
||||
}
|
||||
ch395f_read_recv_buf(E4_SOCK, &s_e4_acc[total], n);
|
||||
total += (uint32_t)n;
|
||||
rl = ch395f_get_recv_len(E4_SOCK);
|
||||
}
|
||||
uint16_t glob = ch395f_get_glob_int_status_all();
|
||||
if ((glob & (1U << (E4_SOCK + 4U))) != 0U) {
|
||||
si = ch395f_get_sock_int_status(E4_SOCK);
|
||||
if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) {
|
||||
death = 1;
|
||||
}
|
||||
}
|
||||
if (!death) {
|
||||
HAL_Delay(5);
|
||||
}
|
||||
}
|
||||
uint32_t loop_total = total;
|
||||
/* 事后读空(取证:数据是否其实还在芯片缓冲里) */
|
||||
for (int k = 0; k < 20; k++) {
|
||||
uint16_t rl = ch395f_get_recv_len(E4_SOCK);
|
||||
if (rl == 0U) {
|
||||
break;
|
||||
}
|
||||
uint32_t room = (uint32_t)sizeof(s_e4_acc) - total;
|
||||
if (room == 0U) {
|
||||
break;
|
||||
}
|
||||
uint16_t n = (rl > 2048U) ? 2048U : rl;
|
||||
if ((uint32_t)n > room) {
|
||||
n = (uint16_t)room;
|
||||
}
|
||||
ch395f_read_recv_buf(E4_SOCK, &s_e4_acc[total], n);
|
||||
total += (uint32_t)n;
|
||||
}
|
||||
/* 载荷自证:图案种子随全局轮次变,上一轮残渣必失配 */
|
||||
uint8_t seed = e4_seed(rg);
|
||||
int mm = -1;
|
||||
for (uint32_t i = 0U; i < loop_total; i++) {
|
||||
if (s_e4_acc[i] != (uint8_t)(seed + i)) {
|
||||
mm = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int ok = (loop_total == E4_PAYLOAD) && (mm < 0);
|
||||
DBG_INFO("BENCH E4 rg%u var=%c VERIFY loop=%lu extra=%lu mm=%d death=%d head=%02X %02X %02X %02X => %s",
|
||||
rg, (char)('A' + variant), (unsigned long)loop_total,
|
||||
(unsigned long)(total - loop_total), mm, death,
|
||||
s_e4_acc[0], s_e4_acc[1], s_e4_acc[2], s_e4_acc[3], ok ? "OK" : "FAIL");
|
||||
/* 轮后清洁 */
|
||||
(void)ch395f_close_socket(E4_SOCK);
|
||||
(void)bench_wait_closed(E4_SOCK, 2000U);
|
||||
(void)bench_drain_events(E4_SOCK, 300U);
|
||||
HAL_Delay(200);
|
||||
}
|
||||
|
||||
static void bench_e4(void) {
|
||||
DBG_INFO("BENCH E4 ===== sock_reset A/B/C x%u (host: ch395f_reuse_host.py --mode e4) =====",
|
||||
(unsigned)E4_ROUNDS);
|
||||
unsigned rg = 0U;
|
||||
for (uint8_t v = 0U; v < 3U; v++) {
|
||||
for (unsigned r = 0U; r < E4_ROUNDS; r++, rg++) {
|
||||
bench_e4_round(rg, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ch395f_reuse_bench(void) {
|
||||
DBG_INFO("BENCH ===== N30 chip-level reuse bench START =====");
|
||||
DBG_INFO("BENCH ===== N34 E2/E3/E4 chip bench START (legacy matrix parked) =====");
|
||||
if (bench_chip_init() != 0) {
|
||||
DBG_ERROR("BENCH init failed, giving up (normal boot continues)");
|
||||
return;
|
||||
}
|
||||
DBG_INFO("BENCH chip ready, cycling %u variants forever", (unsigned)BENCH_N_VARS);
|
||||
for (unsigned i = 0; i < 2048U; i++) {
|
||||
s_tx_pat[i] = (uint8_t)(0xA5U + (i % 251U));
|
||||
}
|
||||
unsigned idx = 0;
|
||||
bench_dwt_enable();
|
||||
if (s_bench_legacy != 0) {
|
||||
DBG_INFO("BENCH legacy N-30 matrix cycling %u variants", (unsigned)BENCH_N_VARS);
|
||||
unsigned idx = 0;
|
||||
for (;;) {
|
||||
bench_round(idx);
|
||||
idx = (idx + 1U) % (unsigned)BENCH_N_VARS;
|
||||
}
|
||||
}
|
||||
bench_e2();
|
||||
bench_e3();
|
||||
bench_e4();
|
||||
DBG_INFO("BENCH ===== E2/E3/E4 ALL DONE (reflash or power-cycle to exit) =====");
|
||||
for (;;) {
|
||||
bench_round(idx);
|
||||
idx = (idx + 1U) % (unsigned)BENCH_N_VARS;
|
||||
HAL_Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
# N-30 芯片级复用时序台架:主机端(与 test/board/ch395f_reuse_bench.c 共用变体表)
|
||||
# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N]
|
||||
# 纯 TCP,无 FTP。按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。
|
||||
# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] [--mode legacy|e4]
|
||||
# legacy: 按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。
|
||||
# e4: N34 E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接——
|
||||
# ①污染:连上发 3000B 后 RST(SO_LINGER 0,Trap 23 中止形态);
|
||||
# ②载荷:立即重连发 4096B 图案(pat[i]=(seed+i)&0xFF, seed=(rg*97+13)&0xFF)后 FIN。
|
||||
# 端口 5100+rg,rg=0..(3*rounds-1),变体顺序 A/B/C(与固件 bench 表一致)。
|
||||
import argparse
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
@@ -70,12 +75,73 @@ def run_round(host, idx, tx_len, close_mode):
|
||||
return ok
|
||||
|
||||
|
||||
def connect_retry(host, port, budget_s):
|
||||
"""connect with retry until budget_s elapses; None on give-up"""
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < budget_s:
|
||||
try:
|
||||
return socket.create_connection((host, port), timeout=5)
|
||||
except OSError:
|
||||
time.sleep(0.2)
|
||||
return None
|
||||
|
||||
|
||||
def e4_round(host, rg, rounds_per_variant, first_budget):
|
||||
"""one E4 round: contaminate (send 3000B then RST) + payload (4096B pattern then FIN)"""
|
||||
port = 5100 + rg
|
||||
seed = (rg * 97 + 13) & 0xFF
|
||||
budget = first_budget if rg == 0 else 30
|
||||
s = connect_retry(host, port, budget)
|
||||
if s is None:
|
||||
print("%s E4 r%2d port%5d <<<< contam CONNECT timeout" % (ts(), rg, port))
|
||||
return False
|
||||
try:
|
||||
s.sendall(b"\x33" * 3000)
|
||||
except OSError as e:
|
||||
print("%s E4 r%2d contam send err %r" % (ts(), rg, e))
|
||||
try:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
|
||||
except OSError:
|
||||
pass
|
||||
s.close() # RST: abort mid-session like ftp_abort_probe (Trap 23 form)
|
||||
time.sleep(0.05)
|
||||
|
||||
s = connect_retry(host, port, 20)
|
||||
if s is None:
|
||||
print("%s E4 r%2d port%5d <<<< payload CONNECT timeout" % (ts(), rg, port))
|
||||
return False
|
||||
payload = bytes(((seed + i) & 0xFF) for i in range(4096))
|
||||
try:
|
||||
s.sendall(payload)
|
||||
except OSError as e:
|
||||
print("%s E4 r%2d payload send err %r" % (ts(), rg, e))
|
||||
s.close()
|
||||
return False
|
||||
s.close() # FIN: full payload delivered, chip drains
|
||||
print("%s E4 r%2d var=%s port%5d contam+payload sent (seed=%02X)"
|
||||
% (ts(), rg, "ABC"[rg // rounds_per_variant], port, seed))
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--host", default="192.168.1.100")
|
||||
ap.add_argument("--cycles", type=int, default=3)
|
||||
ap.add_argument("--mode", choices=["legacy", "e4"], default="legacy")
|
||||
ap.add_argument("--rounds", type=int, default=10, help="e4: rounds per variant")
|
||||
ap.add_argument("--first-budget", type=int, default=300,
|
||||
help="e4: connect budget for round 0 (bench runs E2/E3 first)")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.mode == "e4":
|
||||
fails = []
|
||||
total = 3 * args.rounds
|
||||
for rg in range(total):
|
||||
if not e4_round(args.host, rg, args.rounds, args.first_budget):
|
||||
fails.append(rg)
|
||||
print("E4 RESULT: %d/%d host-side ok, failures %s" % (total - len(fails), total, fails))
|
||||
return 1 if fails else 0
|
||||
|
||||
fails = []
|
||||
for c in range(args.cycles):
|
||||
for idx in sorted(VARS):
|
||||
|
||||
Reference in New Issue
Block a user