/****************************************************************************** * 模块名称:CH395F 链路延迟测试(实现) * 模块功能:DWT CYCCNT 微秒时基 + 采样统计(min/med/p95/max/尖峰)+ [LAT] * 机器可读报告与 verdict;Phase 12(M1):SPI 字节命令 / DMA 时延 * 基线(L1/L2,硬件层模式,无需 PC)。分段定义与判定基线见 * docs/测试说明/链路延迟测试说明.md。 * 适用平台:STM32F4 系列(CH395F) * 作者:王建锋 * 创建日期:2026-09-22 * 修改记录: * 2026-09-22 王建锋 初版:时基 + 统计/报告 + Phase 12(L1/L2) * * 构建形态: * - 产品构建:三个 Phase 宏均未定义 → 本文件除 include 外全部不参与编译; * - 测试构建(TEST_SUITE_CH395F):按所选 Phase 编译对应代码。 * 所有字符串字面量纯 ASCII([LAT] 报告行),中文仅注释。 ******************************************************************************/ #include #include "net_latency_test.h" #include "ch395f_test.h" /* TEST_CHECK / TEST_REPORT / g_test_stats / Phase 宏 */ #include "ch395f.h" /* 驱动公开 API(L1 字节命令 / L2 DMA) */ #include "net_socket.h" /* net_send / net_recv / NET_MSG_DONTWAIT(Phase 13) */ #include "test_config.h" /* TEST_LATENCY_PROBE(探针编译开关) */ #include "main.h" /* CoreDebug / DWT / SystemCoreClock / HAL_GetTick */ #define DBG_TAG "[LAT_TEST]" #include "dbg_log.h" #if defined(ENABLE_PHASE12_TESTS) || \ defined(ENABLE_PHASE13_TESTS) || \ defined(ENABLE_PHASE14_TESTS) /* ============================================================ * DWT CYCCNT 微秒时基(测试专用) * * 工程内首个 DWT 用户(sys_clock 为纯 HAL tick,无 us 能力)。 * 32 位计数 168MHz 下回绕周期 25.17ms:M1 各段(<5ms)与 M2 * 调用方侧测量(单次 <25ms)用单次读数差即可;跨 25ms 的段 * (L4/L8 整帧与重传尖峰)由 PC 端 peer 计时(说明文档 §2.3)。 * 读取 DWT->CYCCNT 无共享状态,任意任务上下文安全。 * ============================================================ */ static int s_dwt_ready = 0; void lat_time_init(void) { if (s_dwt_ready != 0) { return; } CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; /* 使能 DWT 的前置条件 */ DWT->CYCCNT = 0U; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; s_dwt_ready = 1; } #ifdef ENABLE_PHASE12_TESTS /* Phase 12 专用静态助手(仅该阶段引用;守卫防止单阶段构建的 #177-D) */ static uint32_t lat_cycles(void) { return DWT->CYCCNT; } #endif /* ENABLE_PHASE12_TESTS */ static uint32_t lat_us_from_cycles(uint32_t cycles) { /* 换算用 SystemCoreClock(不写死频率),64 位中间量防溢出 */ return (uint32_t)(((uint64_t)cycles * 1000000UL) / (uint64_t)SystemCoreClock); } #ifdef ENABLE_PHASE12_TESTS static uint32_t lat_elapsed_us(uint32_t c_start) { /* 有效区间:单次测量 < 25.17ms(32 位回绕周期),M1 各段均满足 */ return lat_us_from_cycles(DWT->CYCCNT - c_start); } #endif /* ENABLE_PHASE12_TESTS */ /* 公开读数接口(net_socket.c 的 L3 探针经此访问 DWT,避免直接碰寄存器) */ uint32_t lat_cycles_now(void) { return DWT->CYCCNT; } uint32_t lat_cycles_diff_us(uint32_t c_start) { return lat_us_from_cycles(DWT->CYCCNT - c_start); } /* M3 探针计时标记:DWT(亚微秒) + tick(>25ms 回绕兜底),见头文件说明 */ void lat_mark(lat_tmark_t *p) { p->cyc = DWT->CYCCNT; p->tick = HAL_GetTick(); } uint32_t lat_mark_us(const lat_tmark_t *p) { uint32_t dt_ms = HAL_GetTick() - p->tick; /* 跨度 ≥20ms:DWT 可能已回绕(25.17ms)→ 用 tick 兜底(1ms 分辨率) */ if (dt_ms >= 20U) { return dt_ms * 1000U; } return lat_us_from_cycles(DWT->CYCCNT - p->cyc); } /* ============================================================ * 采样统计(静态数组,零 C 库堆) * ============================================================ */ static uint32_t s_lat_samples[LAT_MAX_SAMPLES]; /* 原始样本(按段复用) */ static uint32_t s_lat_sort[LAT_MAX_SAMPLES]; /* 排序工作区(lat_stat_finish) */ void lat_stat_reset(lat_stats_t *p) { p->n = 0U; p->spikes = 0U; p->mn = 0xFFFFFFFFU; /* 空集哨兵 */ p->med = 0U; p->p95 = 0U; p->mx = 0U; } void lat_stat_add(lat_stats_t *p, uint32_t us, uint32_t spike_th_us) { if (p->n < (uint16_t)LAT_MAX_SAMPLES) { s_lat_samples[p->n] = us; } p->n++; if (us < p->mn) { p->mn = us; } if (us > p->mx) { p->mx = us; } if ((spike_th_us != 0U) && (us >= spike_th_us)) { p->spikes++; } } void lat_stat_finish(lat_stats_t *p) { uint32_t n = (p->n < (uint32_t)LAT_MAX_SAMPLES) ? (uint32_t)p->n : (uint32_t)LAT_MAX_SAMPLES; uint32_t i, j; uint32_t idx; for (i = 0U; i < n; i++) { s_lat_sort[i] = s_lat_samples[i]; } /* 插入排序(n<=128,O(n^2) 可接受;零堆) */ for (i = 1U; i < n; i++) { uint32_t key = s_lat_sort[i]; j = i; while ((j > 0U) && (s_lat_sort[j - 1U] > key)) { s_lat_sort[j] = s_lat_sort[j - 1U]; j--; } s_lat_sort[j] = key; } p->med = (n > 0U) ? s_lat_sort[n / 2U] : 0U; idx = (n * 95U) / 100U; if (idx >= n) { idx = (n > 0U) ? (n - 1U) : 0U; } p->p95 = (n > 0U) ? s_lat_sort[idx] : 0U; } void lat_stat_report(const char *p_seg, const lat_stats_t *p, uint32_t warn_med_us, uint32_t fail_med_us, uint16_t max_spikes, int *p_verdict) { int v = LAT_VERDICT_OK; DBG_INFO("[LAT] seg=%s n=%u min=%lu med=%lu p95=%lu max=%lu us spikes=%u", p_seg, (unsigned)p->n, (unsigned long)p->mn, (unsigned long)p->med, (unsigned long)p->p95, (unsigned long)p->mx, (unsigned)p->spikes); if (p->n == 0U) { v = LAT_VERDICT_FAIL; /* 空集 = 测量未发生,判 FAIL */ } else if (p->med > fail_med_us) { v = LAT_VERDICT_FAIL; } else if (p->spikes > max_spikes) { v = LAT_VERDICT_FAIL; } else if (p->med > warn_med_us) { v = LAT_VERDICT_WARN; /* 超 warn 未超 fail:计 PASS,提示重锁阈值 */ } DBG_INFO("[LAT] verdict=%s seg=%s (med=%lums warn<=%lu crit<=%lu spikes=%u/%u)", (v == LAT_VERDICT_OK) ? "OK" : ((v == LAT_VERDICT_WARN) ? "WARN" : "FAIL"), p_seg, (unsigned long)p->med, (unsigned long)warn_med_us, (unsigned long)fail_med_us, (unsigned)p->spikes, (unsigned)max_spikes); if (p_verdict != NULL) { *p_verdict = v; } } /* ============================================================ * TEST_LATENCY_PROBE 探针(M2/M3) * * netTask 上下文(net_socket.c 发送分块循环)向 testTask 报告段内 * 打点(L3 chunk_wait 等调用方侧无法测量的段)。 * 并发模型:单写者(netTask)/单读者(testTask); * - s_probe_n 为 32 位(Cortex-M4 单字读写原子,无锁) * - 样本写入 → n++ 的顺序保证读者看到一致的 (n, samples[0..n)) * - disarm 后 netTask 最多再补 1 个在飞样本(统计已定稿,忽略) * 探针用独立样本/排序数组,不与 lat_stat_* 的工作区共享。 * ============================================================ */ #if defined(TEST_LATENCY_PROBE) static char s_probe_seg[24]; static uint32_t s_probe_max; static volatile uint32_t s_probe_n; /* 32 位原子:netTask 写 / testTask 读 */ static uint32_t s_probe_spike_th; static uint32_t s_probe_spikes; static uint32_t s_probe_mn; static uint32_t s_probe_mx; static uint8_t s_probe_armed; static uint32_t s_probe_samples[LAT_MAX_SAMPLES]; static uint32_t s_probe_sort[LAT_MAX_SAMPLES]; static lat_stats_t s_probe_stats; uint8_t lat_probe_armed(void) { return s_probe_armed; } void lat_probe_arm(const char *seg, uint32_t max_n, uint32_t spike_th_us) { uint32_t i; if (max_n > (uint32_t)LAT_MAX_SAMPLES) { max_n = (uint32_t)LAT_MAX_SAMPLES; } s_probe_max = max_n; s_probe_n = 0U; s_probe_spikes = 0U; s_probe_mn = 0xFFFFFFFFU; s_probe_mx = 0U; s_probe_spike_th = spike_th_us; s_probe_armed = 0U; /* 最后置位:arm 完成前 netTask 不写入 */ for (i = 0U; i < sizeof(s_probe_seg) - 1U; i++) { s_probe_seg[i] = seg[i]; if (s_probe_seg[i] == '\0') { break; } } s_probe_seg[i] = '\0'; s_probe_armed = 1U; } void lat_probe_add(uint32_t us) { if (s_probe_armed == 0U) { return; } if (s_probe_n >= s_probe_max) { return; } s_probe_samples[s_probe_n] = us; if (us < s_probe_mn) { s_probe_mn = us; } if (us > s_probe_mx) { s_probe_mx = us; } if ((s_probe_spike_th != 0U) && (us >= s_probe_spike_th)) { s_probe_spikes++; } s_probe_n++; } uint32_t lat_probe_count(void) { return s_probe_n; } /* 段名比较(避免为一处探针引入 依赖) */ static int lat_probe_seg_is(const char *seg) { uint32_t i; for (i = 0U; i < sizeof(s_probe_seg); i++) { if (s_probe_seg[i] != seg[i]) { return 0; } if (seg[i] == '\0') { return 1; } } return 0; } void lat_probe_add_seg(const char *seg, uint32_t us) { if (s_probe_armed == 0U) { return; } if (lat_probe_seg_is(seg) == 0) { return; /* 当前窗口不是本段 → 丢弃(防跨段污染) */ } lat_probe_add(us); } void lat_probe_disarm(void) { uint32_t n = s_probe_n; uint32_t i, j; uint32_t idx; s_probe_armed = 0U; for (i = 0U; i < n; i++) { s_probe_sort[i] = s_probe_samples[i]; } for (i = 1U; i < n; i++) { uint32_t key = s_probe_sort[i]; j = i; while ((j > 0U) && (s_probe_sort[j - 1U] > key)) { s_probe_sort[j] = s_probe_sort[j - 1U]; j--; } s_probe_sort[j] = key; } s_probe_stats.n = (uint16_t)n; s_probe_stats.mn = (n > 0U) ? s_probe_mn : 0U; s_probe_stats.mx = s_probe_mx; s_probe_stats.med = (n > 0U) ? s_probe_sort[n / 2U] : 0U; idx = (n * 95U) / 100U; if (idx >= n) { idx = (n > 0U) ? (n - 1U) : 0U; } s_probe_stats.p95 = (n > 0U) ? s_probe_sort[idx] : 0U; s_probe_stats.spikes = (uint16_t)s_probe_spikes; } const lat_stats_t *lat_probe_stats(void) { return &s_probe_stats; } #endif /* TEST_LATENCY_PROBE */ /* ============================================================ * Phase 12 — SPI 时延基线(L1/L2,硬件层模式,无需 PC) * * 测量对象(见 链路延迟测试说明.md §1): * L1 SPI 字节命令 —— 3 个代表性命令的完整 SPI 事务时长 * (公开函数内部含 spi_begin/spi_end,即一次完整事务) * L2 SPI DMA —— ch395f_read_recv_buf(N) 完整 DMA 事务 * (读方向:MCU 主发时钟,芯片侧输出内容不影响传输时长; * 硬件层模式 socket 未打开,读方向不改芯片任何状态, * 保持非侵入。写方向 DMA 机制相同(s_spi2_dma_tx_buf), * 由 Phase 13(M2)发送分块握手覆盖) * * 前置:ch395f_test_run() 硬件层分支已完成 net_stack_bring_up() * 并独占 CH395F(无 net_poll 并发 SPI 事务)。 * 初始阈值:静态推算值,台架首跑后重锁(说明文档 §4.1/§8)。 * ============================================================ */ #ifdef ENABLE_PHASE12_TESTS #define LAT_P12_CMD_N 100U /* L1 每命令样本数 */ #define LAT_P12_DMA_N 100U /* L2 每长度档样本数 */ #define LAT_P12_DMA_LEN_MAX 2048U /* L2 单发上限 = CH395F_SPI_DMA_MAX_PAYLOAD */ /* L1 阈值(us)—— 2026-09-22 台架首跑后重锁(实测 med 10~15us; * warn ≈ 2×med,crit ≈ 2×warn,见 链路延迟测试说明.md §4.1) */ #define LAT_P12_CMD_WARN_US 40U #define LAT_P12_CMD_FAIL_US 100U static const char *const a_p12_cmd_seg[3] = { "spi_cmd_getver", /* GET_IC_VER:1 写 + 1 读 */ "spi_cmd_chkexist", /* CHECK_EXIST:1 写 + 1 读(回显取反自检) */ "spi_cmd_gint16", /* GET_GLOB_INT_STATUS:1 写 + 2 读(读即清) */ }; static const uint16_t a_p12_dma_len[4] = { 4U, 64U, 1024U, 2048U }; static const char *const a_p12_dma_seg[4] = { "spi_dma_4", "spi_dma_64", "spi_dma_1024", "spi_dma_2048" }; /* 2026-09-22 台架首跑后重锁(实测 med 20/114/1611/3208us; * warn ≈ 2×med,crit ≈ 2×warn) */ static const uint32_t a_p12_dma_warn[4] = { 40U, 250U, 3200U, 6400U }; static const uint32_t a_p12_dma_fail[4] = { 100U, 600U, 6400U, 12800U }; #endif /* ENABLE_PHASE12_TESTS */ #ifdef ENABLE_PHASE12_TESTS void net_latency_phase12_run(void) { lat_stats_t st; static uint8_t s_dma_buf[LAT_P12_DMA_LEN_MAX]; /* DMA 读输出缓冲(静态,零堆) */ int v; int v_all = LAT_VERDICT_OK; uint32_t i; uint32_t k; /* Phase 12 为硬件层运行的最后阶段:重置全局测试统计不影响 * 前序阶段已打印的 TEST_REPORT,使 Phase 12 计数独立清晰 */ memset(&g_test_stats, 0U, sizeof(g_test_stats)); DBG_INFO("=== CH395F Phase 12 Tests (link latency baseline, L1/L2) ==="); lat_time_init(); DBG_INFO("[LAT] run_id=%lu phase=12", (unsigned long)HAL_GetTick()); /* 时基校准行:台架可用逻辑分析仪核对 us/cycle 换算 */ DBG_INFO("[LAT] cal core_clk=%lu us_per_1024cyc=%lu", (unsigned long)SystemCoreClock, (unsigned long)(((uint64_t)1024U * 1000000UL) / (uint64_t)SystemCoreClock)); /* ---- 前置:芯片存活(无芯片则无法测量,判 FAIL 跳过) ---- */ { ch395f_status_t ret = ch395f_check_exist(); if (ret != CH395F_ERR_SUCCESS) { DBG_ERROR("[LAT] chip not detected (0x%02X), Phase 12 skipped", (uint8_t)ret); TEST_CHECK(0, "P12 pre: ch395f_check_exist"); DBG_INFO("[LAT] summary verdict=FAIL"); DBG_INFO("[LAT] report complete"); TEST_REPORT("Phase 12"); return; } } memset(s_dma_buf, 0U, sizeof(s_dma_buf)); /* ---- L1:SPI 字节命令时延(3 个代表性命令 × 100) ---- */ for (i = 0U; i < 3U; i++) { lat_stat_reset(&st); for (k = 0U; k < LAT_P12_CMD_N; k++) { uint32_t c0 = lat_cycles(); switch (i) { case 0U: (void)ch395f_get_version(); break; case 1U: (void)ch395f_check_exist(); break; default: (void)ch395f_get_glob_int_status_all(); break; } lat_stat_add(&st, lat_elapsed_us(c0), 0U); } lat_stat_finish(&st); lat_stat_report(a_p12_cmd_seg[i], &st, LAT_P12_CMD_WARN_US, LAT_P12_CMD_FAIL_US, 0U, &v); if (v > v_all) { v_all = v; } } /* ---- L2:SPI DMA 时延(4 档长度 × 100) ---- */ for (i = 0U; i < 4U; i++) { lat_stat_reset(&st); for (k = 0U; k < LAT_P12_DMA_N; k++) { uint32_t c0 = lat_cycles(); (void)ch395f_read_recv_buf(0U, s_dma_buf, a_p12_dma_len[i]); lat_stat_add(&st, lat_elapsed_us(c0), 0U); } lat_stat_finish(&st); lat_stat_report(a_p12_dma_seg[i], &st, a_p12_dma_warn[i], a_p12_dma_fail[i], 0U, &v); if (v > v_all) { v_all = v; } } /* ---- 汇总 ---- */ { const char *p_name = (v_all == LAT_VERDICT_OK) ? "OK" : ((v_all == LAT_VERDICT_WARN) ? "WARN" : "FAIL"); DBG_INFO("[LAT] summary verdict=%s", p_name); } /* WARN 计 PASS(提示重锁阈值);FAIL 才使套件失败 */ TEST_CHECK(v_all != LAT_VERDICT_FAIL, "P12 latency baseline (verdict=%s)", (v_all == LAT_VERDICT_OK) ? "OK" : ((v_all == LAT_VERDICT_WARN) ? "WARN" : "FAIL")); DBG_INFO("[LAT] report complete"); TEST_REPORT("Phase 12"); } #else /* ENABLE_PHASE12_TESTS */ void net_latency_phase12_run(void) { /* 本构建未选中 Phase 12:空实现(符号保留,行为不参与) */ } #endif /* ENABLE_PHASE12_TESTS */ #ifdef ENABLE_PHASE13_TESTS /* ==================================================================== * Phase 13 — 芯片 + 队列时延(L3~L7/L10,需 PC peer,M2) * * 由 net_layer_test_main() 监听循环(8080 端口,~10ms 节拍)事件驱动。 * 服务器驱动协议:MCU 发操作字节,peer(test/latency_peer.py --mode net) * 执行对应流量;段完成后 MCU 打 [LAT] 报告行并推进下一操作。 * * 段定义(说明文档 §1): * L5 queue_rt_recv / queue_rt_send —— 调用方视角 net_recv/net_send * 全程(入队 + netTask 调度 + 处理 + 通知),静默基线档; * 压力档 = peer --ftp-stress(lftpd 阻塞读占住 netTask → E-02 复现) * L6 req2first_mcu —— 请求收齐 → 应答发送完成(MCU 侧,含队列+1 chunk) * L7 fullframe_mcu —— 9B LCDMEM 请求 → 3209B 全帧发送完成 * (netTask 内 4 次分块停止等待,= 生产路径发送段) * L3 chunk_wait —— netTask 内"写发送缓冲 → SENBUF_FREE"等待 * (调用方不可见,经 TEST_LATENCY_PROBE 探针,见 net_socket.c) * L10 logtax_line —— 一行 60 字符 printf(fputc 逐字节 @115200) * * 执行顺序(2026-09-22 run3 后重排):L5 → L3 → L10 → L6 → L7 —— * 失步敏感的小帧 RX 协议段(L6/L7)放最后,先锁住抗失步段的基线。 * 对端侧(L4 线上传输/整帧到达/尖峰)由 peer JSON 报告,按主机时间戳关联。 * * 阈值:初始值,台架首跑后重锁(说明文档 §4.2)。 * 独立运行:与其他 Phase 共用 8080 socket,混跑会污染帧测量。 * ==================================================================== */ #define LAT_P13_SAMPLES 10U /* L6/L7 每段样本数(2026-09-22 run3 实证:L6 50 轮 * 小交换后芯片 TCP 失步 → TX 停滞 + 重复递交 9B 碎片 * → 降至 10,先取干净基线再量化失步边界) */ #define LAT_P13_L5_CHUNKS 20U /* L5 flood 块数(2026-09-22 run1 实证:50×100B@5ms * 达 20KB/s 饱和芯片接收缓冲 → 窗口关闭重传 → * 重复递交 11884/5000B → 对端 RST;降至 1KB/s * 静默档,见 链路延迟测试说明.md §8 run1 记录) */ #define LAT_P13_L5_CHUNK_LEN 100U #define LAT_P13_L3_SENDS 16U /* 每次 4KB = 4 chunk → 16×4 = 64 探针样本 * (run3 后从 32 减半:控制持续 TX 总量) */ #define LAT_P13_L3_SEND_LEN 4096U #define LAT_P13_L3_SAMPLES (LAT_P13_L3_SENDS * 4U) #define LAT_P13_L6_SIZE 10U #define LAT_P13_L7_REQ_LEN 9U #define LAT_P13_FRAME_LEN 3209U /* 4B 头 + 3204B 数据段(偏移u32+位图3200B) + 1B CRC */ #define LAT_P13_RECV_LEN 256U #define LAT_P13_TIMEOUT_MS 240000U /* 整体会话超时(压力档含 30s 冻结) */ #define LAT_P13_SAMPLE_T_MS 35000U /* 单样本等待超时(覆盖一个 30s 冻结) */ /* 初始阈值(us):warn ≈ 2~3×预期,crit 覆盖硬失败(DMA/发送超时 1s 必超) */ #define LAT_P13_QRT_WARN_US 10000U #define LAT_P13_QRT_CRIT_US 100000U #define LAT_P13_R2F_WARN_US 20000U #define LAT_P13_R2F_CRIT_US 200000U #define LAT_P13_FF_WARN_US 50000U #define LAT_P13_FF_CRIT_US 500000U #define LAT_P13_CW_WARN_US 10000U #define LAT_P13_CW_CRIT_US 500000U #define LAT_P13_CW_SPIKE_US 500000U /* ≥500ms = 重传粒度指纹(D1) */ #define LAT_P13_LT_WARN_US 10000U #define LAT_P13_LT_CRIT_US 25000U /* 操作字节(MCU → peer) */ #define LAT_P13_OP_L5_RECV 0x01U #define LAT_P13_OP_L5_SEND 0x02U #define LAT_P13_OP_L6 0x03U #define LAT_P13_OP_L7 0x04U #define LAT_P13_OP_L3 0x05U #define LAT_P13_OP_DONE 0xFFU /* HELLO 魔术字(peer → MCU,4 字节) */ static const uint8_t a_p13_hello[4] = { 0x5AU, 0xA5U, 0x58U, 0x01U }; enum { P13_ST_HELLO = 0, P13_ST_L5_RECV, P13_ST_L5_SEND, P13_ST_L6, P13_ST_L7, P13_ST_L3, P13_ST_L10, P13_ST_DONE, P13_ST_ABORT }; static uint8_t s_p13_state = P13_ST_HELLO; static uint8_t s_p13_done = 0U; /* 会话完成/中止:后续调用空操作 */ static uint8_t s_p13_resync_done = 0U; /* abort 后首次重同步只执行一次 */ static uint8_t s_p13_clean_done = 0U; /* 会话干净完成(全段 OK + 已发 0xFF) */ static uint32_t s_p13_resync_tick = 0U; /* 最近一次(重)监听的时刻(周期自愈用) */ static uint8_t s_p13_hello_n = 0U; static int s_p13_verdict_all = LAT_VERDICT_OK; static int s_p13_seg_force_fail = 0; /* 当前段出现短写/错误 → 强制 FAIL */ static uint32_t s_p13_start_tick; static uint32_t s_p13_step_tick; /* L6/L7 单样本等待计时 */ static uint32_t s_p13_stage; static uint32_t s_p13_rx_bytes; static uint16_t s_p13_l6rx_n; static uint16_t s_p13_l7rx_n; static lat_stats_t s_p13_st; static uint8_t s_p13_rx[LAT_P13_RECV_LEN]; static uint8_t s_p13_tx[LAT_P13_L3_SEND_LEN]; /* L5/L3 flood 载荷(模式预填) */ static uint8_t s_p13_frame[LAT_P13_FRAME_LEN]; /* L7 应答帧(建一次) */ static uint8_t s_p13_l6rx[LAT_P13_L6_SIZE]; static uint8_t s_p13_l7rx[LAT_P13_L7_REQ_LEN]; /* 构造 L7 应答帧(严格照生产 RemoDispBus 帧格式,ui_net_sink.c remo_send): * [TAG=0xBB][CMD=0x03][LEN=3204 大端][DATA = 偏移u32(0) + 1bpp 位图 3200B][CRC] * CRC = 仅对 DATA 段(3204B)逐字节异或;整帧 = 4 + 3204 + 1 = 3209B */ static void p13_build_frame(void) { uint32_t i; uint8_t crc = 0U; s_p13_frame[0] = 0xBBU; s_p13_frame[1] = 0x03U; s_p13_frame[2] = 0x0CU; /* 3204 = 0x0C84,大端高字节 */ s_p13_frame[3] = 0x84U; s_p13_frame[4] = 0U; /* 数据段首 4B = 起始偏移,大端 0 */ s_p13_frame[5] = 0U; s_p13_frame[6] = 0U; s_p13_frame[7] = 0U; for (i = 0U; i < 3200U; i++) { s_p13_frame[8U + i] = (uint8_t)(i & 0xFFU); } for (i = 0U; i < 4U + 3200U; i++) { crc ^= s_p13_frame[4U + i]; /* CRC 覆盖数据段(索引 4..3207) */ } s_p13_frame[8U + 3200U] = crc; /* 索引 3208 = 帧尾 CRC */ } static uint8_t p13_send_op(int32_t sockfd, uint8_t op) { int n = net_send(sockfd, &op, 1, 0); if (n != 1) { /* 协议操作字节发送失败 = 与 peer 的状态机同步永久丢失(run9 实证: * op 静默丢失 → peer 空等 300s 超时 → 整段会话作废)→ 必须可见 */ DBG_INFO("[LAT] note: op 0x%02X send failed n=%d errno=%d", op, n, net_get_errno()); } return (n == 1) ? 1U : 0U; } /* 当前段收尾:报告 + verdict 累计 + 统计容器复位 */ static void p13_report_seg(const char *p_seg, uint32_t warn_us, uint32_t crit_us, uint16_t max_spikes) { int v = LAT_VERDICT_OK; if (s_p13_seg_force_fail != 0) { v = LAT_VERDICT_FAIL; } lat_stat_finish(&s_p13_st); lat_stat_report(p_seg, &s_p13_st, warn_us, crit_us, max_spikes, &v); if (s_p13_seg_force_fail != 0) { DBG_INFO("[LAT] note: %s has send error/short write -> FAIL", p_seg); } if (v > s_p13_verdict_all) { s_p13_verdict_all = v; } s_p13_seg_force_fail = 0; lat_stat_reset(&s_p13_st); } /* 会话中止:尽力报告进行中段的 partial 统计 + FAIL 汇总(标记链不断) */ static void p13_abort(const char *p_why) { const char *p_name = "FAIL"; if (s_p13_stage > 0U) { lat_stat_finish(&s_p13_st); DBG_INFO("[LAT] seg=partial n=%u min=%lu med=%lu p95=%lu max=%lu us (interrupted)", (unsigned)s_p13_st.n, (unsigned long)s_p13_st.mn, (unsigned long)s_p13_st.med, (unsigned long)s_p13_st.p95, (unsigned long)s_p13_st.mx); s_p13_verdict_all = LAT_VERDICT_FAIL; } #if defined(TEST_LATENCY_PROBE) if (lat_probe_armed() != 0U) { const lat_stats_t *p_ps; lat_probe_disarm(); p_ps = lat_probe_stats(); DBG_INFO("[LAT] seg=chunk_wait n=%u min=%lu med=%lu p95=%lu max=%lu us spikes=%u (partial, interrupted)", (unsigned)p_ps->n, (unsigned long)p_ps->mn, (unsigned long)p_ps->med, (unsigned long)p_ps->p95, (unsigned long)p_ps->mx, (unsigned)p_ps->spikes); s_p13_verdict_all = LAT_VERDICT_FAIL; } #endif DBG_ERROR("[LAT] phase13 abort: %s", p_why); TEST_CHECK(0, "P13 abort: %s", p_why); DBG_INFO("[LAT] summary verdict=%s", p_name); DBG_INFO("[LAT] report complete"); TEST_REPORT("Phase 13"); s_p13_done = 1U; s_p13_state = P13_ST_ABORT; } /* HELLO 确认后的公共入口(首连与重启共用):复位会话进度 → 发 op 0x01 进 L5 */ static void p13_on_hello(int32_t sockfd) { s_p13_hello_n = 0U; DBG_INFO("[LAT] peer session start"); lat_stat_reset(&s_p13_st); s_p13_stage = 0U; s_p13_rx_bytes = 0U; s_p13_state = P13_ST_L5_RECV; (void)p13_send_op(sockfd, LAT_P13_OP_L5_RECV); } /* 会话重启初始化(abort 后 peer 重连再 HELLO 时调用): * 2026-09-22 run6 实证:芯片对空闲连接自行超时发 RST(MCU 网络栈慢于 * 芯片握手完成时,连接建立后 15~30s 内无流量即被 RST)→ peer 重连 + * 本重启 = 闭环,避免一次慢启动毁掉整轮测试 */ static void p13_reinit(void) { #if defined(TEST_LATENCY_PROBE) if (lat_probe_armed() != 0U) { lat_probe_disarm(); } #endif s_p13_state = P13_ST_HELLO; /* 由 p13_on_hello 覆盖为 L5_RECV */ s_p13_stage = 0U; s_p13_rx_bytes = 0U; s_p13_l6rx_n = 0U; s_p13_l7rx_n = 0U; s_p13_step_tick = HAL_GetTick(); s_p13_start_tick = HAL_GetTick(); /* 整体 240s 预算按重启重新计时 */ s_p13_seg_force_fail = 0; s_p13_verdict_all = LAT_VERDICT_OK; s_p13_done = 0U; s_p13_resync_done = 0U; s_p13_clean_done = 0U; s_p13_resync_tick = HAL_GetTick(); s_p13_hello_n = 0U; lat_stat_reset(&s_p13_st); } /* 会话是否活动(供 net_test_task 的监听循环判断:活动期跳过 net_accept, * 连接由芯片 CONNECT 事件建立 + 本模块 HELLO/重连轮询接管,run8 实证: * 10ms 一次 accept 与协议消息争抢 netTask 队列并产生大量陈旧通知噪声) */ int net_latency_phase13_busy(void) { #ifdef ENABLE_PHASE13_TESTS return (s_p13_done == 0U || s_p13_state == (uint8_t)P13_ST_ABORT) ? 1 : 0; #else return 0; #endif } void net_latency_phase13_run(int32_t sockfd) { #ifdef ENABLE_PHASE13_TESTS int32_t n; uint32_t i; uint32_t c0; uint32_t us; if (s_p13_done != 0U) { if (s_p13_state != P13_ST_ABORT) { return; /* 正常完成/DONE:会话终态,不再处理 */ } /* 重同步(run7~run9 实证):peer 断连后芯片 Socket 去向不确定 * (静默回收回监听 / RST 关闭 / 监听器丢失)→ 进 abort 时做一次 * 分状态重同步 + 等待期周期性自愈: * ESTABLISHED(陈旧)→ 先只修 MCU 侧状态(run8:close 会摧毁芯片 * 侧健康监听器 → 连接永拒),再无条件芯片侧重监听(open+listen * 含 Trap 09 重试;run9:一次性 net_listen 落 Trap 09 窗口失败 * 且被忽略 → 监听器永久丢失); * CLOSED → 芯片命令重新 LISTEN; * LISTENING → 10s 无新连接则再重监听一次(自愈静默丢失的监听器; * 重监听监听窗口中断 ~1s,peer 重连重试可吸收)。 */ if (s_p13_resync_done == 0U) { int st = net_sock_state(sockfd); s_p13_resync_done = 1U; s_p13_resync_tick = HAL_GetTick(); if (st == NET_SOCK_STATE_ESTABLISHED) { DBG_INFO("[LAT] note: stale ESTABLISHED on abort -> resync + relisten"); (void)net_resync_listen(sockfd); (void)net_relisten(sockfd); } else if (st == NET_SOCK_STATE_CLOSED) { DBG_INFO("[LAT] note: CLOSED on abort -> re-listen via chip"); (void)net_relisten(sockfd); } } else if (s_p13_clean_done == 0U) { /* 周期自愈:10s 无新连接(状态仍 LISTENING)→ 再重监听一次; * 新连接到来时状态已转 ESTABLISHED,本分支自然不再触发。 * 干净完成(已发 0xFF、peer 正常退出)后不再自愈——否则每 10s * 拆建一次监听器(无谓 SPI 开销 + 日志噪声 + 台架永不判空闲, * run12 实证),此时候只需静候下一次 re-HELLO。 */ uint32_t now = HAL_GetTick(); if (net_sock_state(sockfd) == NET_SOCK_STATE_LISTENING && (uint32_t)(now - s_p13_resync_tick) >= 10000U) { s_p13_resync_tick = now; DBG_INFO("[LAT] note: no peer in 10s -> relisten (self-heal)"); (void)net_relisten(sockfd); } } n = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT); if (n > 0) { for (i = 0U; i + 4U <= (uint32_t)n; i++) { if (s_p13_rx[i] == 0x5AU && s_p13_rx[i + 1U] == 0xA5U && s_p13_rx[i + 2U] == 0x58U && s_p13_rx[i + 3U] == 0x01U) { p13_reinit(); DBG_INFO("[LAT] peer re-HELLO -> session restart"); p13_on_hello(sockfd); /* HELLO 已消耗 → 直接进 L5 并回 op */ break; } } } /* 重启等待总超时(abort 后再给 120s):无 peer 则转终态,停止轮询 */ if (s_p13_done != 0U && (HAL_GetTick() - s_p13_start_tick) > (uint32_t)((uint32_t)LAT_P13_TIMEOUT_MS + 120000U)) { s_p13_state = P13_ST_DONE; } return; } /* ---- 会话起点:时基/帧/载荷一次性准备 ---- */ if (s_p13_state == P13_ST_HELLO && s_p13_start_tick == 0U) { s_p13_start_tick = HAL_GetTick(); lat_time_init(); p13_build_frame(); for (i = 0U; i < LAT_P13_L3_SEND_LEN; i++) { s_p13_tx[i] = (uint8_t)(i & 0xFFU); } DBG_INFO("=== CH395F Phase 13 Tests (chip+queue latency, L3~L7/L10) ==="); DBG_INFO("[LAT] run_id=%lu phase=13 cal core_clk=%lu us_per_1024cyc=%lu", (unsigned long)HAL_GetTick(), (unsigned long)SystemCoreClock, (unsigned long)(((uint64_t)1024U * 1000000UL) / (uint64_t)SystemCoreClock)); DBG_INFO("[LAT] waiting for peer HELLO (port 8080, latency_peer.py --mode net)"); } if ((HAL_GetTick() - s_p13_start_tick) > LAT_P13_TIMEOUT_MS) { p13_abort("overall timeout"); return; } switch (s_p13_state) { /* ---- HELLO:4 字节魔术字(容忍杂散字节:失配即从头对齐) ---- */ case P13_ST_HELLO: { n = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT); if (n == 0) { p13_abort("peer connection closed (EOF)"); return; } if (n > 0) { for (i = 0U; (int32_t)i < n; i++) { uint8_t b = s_p13_rx[i]; if (s_p13_hello_n == 0U && b != a_p13_hello[0U]) { continue; } if (b != a_p13_hello[s_p13_hello_n]) { s_p13_hello_n = 0U; continue; } s_p13_hello_n++; if (s_p13_hello_n == 4U) { p13_on_hello(sockfd); } } } /* n < 0: DONTWAIT 空轮询(WOULDBLOCK = 暂无数据)= 正常态,继续等待 */ break; } /* ---- L5 静默基线:peer flood 50×100B,MCU 逐次 DONTWAIT 收取 ---- * 仅对"收到数据"的调用计时(队列往返 + netTask SPI 读);空轮询不采样 */ case P13_ST_L5_RECV: { c0 = lat_cycles_now(); n = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT); us = lat_cycles_diff_us(c0); if (n == 0) { p13_abort("peer connection closed (L5 recv)"); return; } if (n > 0) { lat_stat_add(&s_p13_st, us, 0U); s_p13_rx_bytes += (uint32_t)n; } /* 重复递交守卫(run1 实证缺陷 D11):芯片接收缓冲饱和后窗口 * 关闭/重开会把重传段重复递交(run1:5000B 送达 11884B,+138%)→ * 超容差即判 FAIL 并落 partial 报告(段内数据仍记录) */ if (s_p13_rx_bytes > (LAT_P13_L5_CHUNKS * LAT_P13_L5_CHUNK_LEN) + (2U * LAT_P13_RECV_LEN)) { p13_abort("L5 duplicate delivery (CH395F TCP retransmit re-delivery)"); return; } if (s_p13_rx_bytes >= (LAT_P13_L5_CHUNKS * LAT_P13_L5_CHUNK_LEN)) { p13_report_seg("queue_rt_recv", LAT_P13_QRT_WARN_US, LAT_P13_QRT_CRIT_US, 0U); s_p13_state = P13_ST_L5_SEND; s_p13_stage = 0U; lat_stat_reset(&s_p13_st); if (p13_send_op(sockfd, LAT_P13_OP_L5_SEND) == 0U) { p13_abort("L5->L5SEND op send failed (peer desync)"); return; } } break; } /* ---- L5 静默基线:MCU 阻塞 net_send 50×100B(调用即含队列往返全程) ---- * 阻塞调用经 xTaskNotifyWait 让出 → netTask(Normal) 自然抢占,无额外延迟 */ case P13_ST_L5_SEND: { while (s_p13_stage < LAT_P13_L5_CHUNKS) { c0 = lat_cycles_now(); n = net_send(sockfd, s_p13_tx, (int32_t)LAT_P13_L5_CHUNK_LEN, 0); us = lat_cycles_diff_us(c0); if (n != (int32_t)LAT_P13_L5_CHUNK_LEN) { s_p13_seg_force_fail = 1; #if defined(TEST_LATENCY_PROBE) DBG_INFO("[LAT] L5 send unexpected n=%d stage=%u", (int)n, (unsigned)s_p13_stage); #endif } lat_stat_add(&s_p13_st, us, 0U); s_p13_stage++; } p13_report_seg("queue_rt_send", LAT_P13_QRT_WARN_US, LAT_P13_QRT_CRIT_US, 0U); /* run3 重排:先 L3(纯 TX flood,1s 超时保护下不挂死)+ L10(本地), * 后 L6/L7(RX 协议段;run3 实证芯片 TCP 对小帧交换易失步 → 靠后并降样本数) */ s_p13_state = P13_ST_L3; s_p13_stage = 0U; break; } /* ---- L6:每样本一轮 op→10B 请求→10B 回显(MCU 侧:收齐→发出) ---- */ case P13_ST_L6: { n = net_recv(sockfd, s_p13_l6rx + s_p13_l6rx_n, (int)(LAT_P13_L6_SIZE - s_p13_l6rx_n), NET_MSG_DONTWAIT); if (n == 0) { p13_abort("peer connection closed (L6)"); return; } if (n > 0) { s_p13_l6rx_n += (uint16_t)n; } if (s_p13_l6rx_n >= LAT_P13_L6_SIZE) { c0 = lat_cycles_now(); n = net_send(sockfd, s_p13_l6rx, (int32_t)LAT_P13_L6_SIZE, 0); /* 回显 */ us = lat_cycles_diff_us(c0); if (n != (int32_t)LAT_P13_L6_SIZE) { s_p13_seg_force_fail = 1; #if defined(TEST_LATENCY_PROBE) DBG_INFO("[LAT] L6 echo unexpected n=%d stage=%u", (int)n, (unsigned)s_p13_stage); #endif } lat_stat_add(&s_p13_st, us, 0U); s_p13_stage++; if (s_p13_stage >= LAT_P13_SAMPLES) { p13_report_seg("req2first_mcu", LAT_P13_R2F_WARN_US, LAT_P13_R2F_CRIT_US, 0U); s_p13_state = P13_ST_L7; s_p13_stage = 0U; s_p13_l7rx_n = 0U; s_p13_step_tick = HAL_GetTick(); (void)p13_send_op(sockfd, LAT_P13_OP_L7); } else { s_p13_l6rx_n = 0U; s_p13_step_tick = HAL_GetTick(); (void)p13_send_op(sockfd, LAT_P13_OP_L6); } } else if ((HAL_GetTick() - s_p13_step_tick) > LAT_P13_SAMPLE_T_MS) { p13_abort("L6 peer timeout"); return; } break; } /* ---- L7:9B LCDMEM 请求 → 3209B 全帧(netTask 内 4 分块停止等待) ---- */ case P13_ST_L7: { n = net_recv(sockfd, s_p13_l7rx + s_p13_l7rx_n, (int)(LAT_P13_L7_REQ_LEN - s_p13_l7rx_n), NET_MSG_DONTWAIT); if (n == 0) { p13_abort("peer connection closed (L7)"); return; } if (n > 0) { s_p13_l7rx_n += (uint16_t)n; } if (s_p13_l7rx_n >= LAT_P13_L7_REQ_LEN) { /* 校验请求帧(严格照生产解析器 ui_net_sink.c remo_handle 的语义): * [TAG=0xAA][CMD=0x03][LEN=4 大端][偏移u32 大端=0][CRC=XOR(数据段 4B)] * 注意 LEN 是"数据段长度"(4),非请求的位图字节数;CRC 只覆盖数据段 */ { uint8_t crc = 0U; uint32_t len16; for (i = 4U; i < 8U; i++) { crc ^= s_p13_l7rx[i]; } len16 = ((uint32_t)s_p13_l7rx[2U] << 8) | (uint32_t)s_p13_l7rx[3U]; if (s_p13_l7rx[0U] != 0xAAU || s_p13_l7rx[1U] != 0x03U || len16 != 4U || crc != s_p13_l7rx[8U] || s_p13_l7rx[4U] != 0U || s_p13_l7rx[5U] != 0U || s_p13_l7rx[6U] != 0U || s_p13_l7rx[7U] != 0U) { p13_abort("L7 bad request frame"); return; } } c0 = lat_cycles_now(); n = net_send(sockfd, s_p13_frame, (int32_t)LAT_P13_FRAME_LEN, 0); us = lat_cycles_diff_us(c0); if (n != (int32_t)LAT_P13_FRAME_LEN) { s_p13_seg_force_fail = 1; } lat_stat_add(&s_p13_st, us, 0U); s_p13_stage++; if (s_p13_stage >= LAT_P13_SAMPLES) { p13_report_seg("fullframe_mcu", LAT_P13_FF_WARN_US, LAT_P13_FF_CRIT_US, 0U); /* L7 为末段(run3 重排后的最终顺序:L5→L3→L10→L6→L7→DONE); * 此处若回跳 L3 会形成 L3→L10→L6→L7 无限循环、永不下发 0xFF, * peer 只能等 300s 超时(run11 实证) */ s_p13_state = P13_ST_DONE; s_p13_stage = 0U; } else { s_p13_l7rx_n = 0U; s_p13_step_tick = HAL_GetTick(); (void)p13_send_op(sockfd, LAT_P13_OP_L7); } } else if ((HAL_GetTick() - s_p13_step_tick) > LAT_P13_SAMPLE_T_MS) { p13_abort("L7 peer timeout"); return; } break; } #if defined(TEST_LATENCY_PROBE) /* ---- L3:探针武装 + 32×4KB 发送 flood(= 128 chunk 样本) ---- * chunk_wait 在 netTask 内测量(写发送缓冲 → SENBUF_FREE), * 调用方侧不可见 → 必须经 net_socket.c 探针(TEST_LATENCY_PROBE) */ case P13_ST_L3: { if (s_p13_stage == 0U) { (void)p13_send_op(sockfd, LAT_P13_OP_L3); lat_probe_arm("chunk_wait", (uint32_t)LAT_P13_L3_SAMPLES, LAT_P13_CW_SPIKE_US); } while (s_p13_stage < LAT_P13_L3_SENDS) { n = net_send(sockfd, s_p13_tx, (int32_t)LAT_P13_L3_SEND_LEN, 0); if (n != (int32_t)LAT_P13_L3_SEND_LEN) { s_p13_seg_force_fail = 1; #if defined(TEST_LATENCY_PROBE) DBG_INFO("[LAT] L3 send unexpected n=%d stage=%u", (int)n, (unsigned)s_p13_stage); #endif } s_p13_stage++; } /* 阻塞发送均"芯片确认本块全部 chunk 完成(SEND_OK)"才返回 → 此刻探针 * 样本已定稿;芯片 TX 失步时以短写记录(1s 超时保护,不会挂死调用方) */ { int v = LAT_VERDICT_OK; const lat_stats_t *p_ps; lat_probe_disarm(); p_ps = lat_probe_stats(); if (s_p13_seg_force_fail != 0) { v = LAT_VERDICT_FAIL; } lat_stat_report("chunk_wait", p_ps, LAT_P13_CW_WARN_US, LAT_P13_CW_CRIT_US, 0U, &v); if (s_p13_seg_force_fail != 0) { DBG_INFO("[LAT] note: chunk_wait has send error/short write -> FAIL"); } if (v > s_p13_verdict_all) { s_p13_verdict_all = v; } if (s_p13_seg_force_fail != 0) { DBG_INFO("[LAT] note: chunk_wait partial n=%u/%u (chip TX stall suspected)", (unsigned)p_ps->n, (unsigned)LAT_P13_L3_SAMPLES); } s_p13_seg_force_fail = 0; s_p13_state = P13_ST_L10; s_p13_stage = 0U; } break; } #else /* TEST_LATENCY_PROBE */ case P13_ST_L3: { /* 探针未编译(未选 Phase 13/14 之外的组合):跳过本段 */ DBG_INFO("[LAT] skip chunk_wait (probe not compiled)"); s_p13_state = P13_ST_L10; s_p13_stage = 0U; break; } #endif /* TEST_LATENCY_PROBE */ /* ---- L10:一行 60 字符 printf 的耗时(fputc 逐字节 @115200,D6 量化) ---- */ case P13_ST_L10: { static char s_line[64]; if (s_p13_stage == 0U) { snprintf(s_line, sizeof(s_line), "LAT logtax line: 0123456789 0123456789 012345"); lat_stat_reset(&s_p13_st); } while (s_p13_stage < LAT_P13_SAMPLES) { c0 = lat_cycles_now(); printf("%s\n", s_line); lat_stat_add(&s_p13_st, lat_cycles_diff_us(c0), 0U); s_p13_stage++; } p13_report_seg("logtax_line", LAT_P13_LT_WARN_US, LAT_P13_LT_CRIT_US, 0U); /* L6 前清空 socket 残留(重传/重复递交字节,run1 实证 6884B、run3 实证 9B 碎片): * 残留会与 L6/L7 的请求帧错位混读 → 协议失步。此窗口对端静默 * (对端只在收到操作字节后才发包),DONTWAIT 抽干即安全。 */ { int32_t d; uint32_t drained = 0U; uint32_t t0 = HAL_GetTick(); while (drained < 65536U && (HAL_GetTick() - t0) < 5000U) { d = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT); if (d <= 0) { break; /* d==0 = 已排空/EOF;d<0 = 空轮询(WOULDBLOCK) */ } drained += (uint32_t)d; } if (drained > 0U) { DBG_INFO("[LAT] note: drained %u residual bytes before L6 (retransmit/duplicate)", (unsigned)drained); } } s_p13_state = P13_ST_L6; s_p13_stage = 0U; s_p13_l6rx_n = 0U; s_p13_step_tick = HAL_GetTick(); (void)p13_send_op(sockfd, LAT_P13_OP_L6); break; } case P13_ST_DONE: { const char *p_name; (void)p13_send_op(sockfd, LAT_P13_OP_DONE); p_name = (s_p13_verdict_all == LAT_VERDICT_OK) ? "OK" : ((s_p13_verdict_all == LAT_VERDICT_WARN) ? "WARN" : "FAIL"); DBG_INFO("[LAT] summary verdict=%s", p_name); TEST_CHECK(s_p13_verdict_all != LAT_VERDICT_FAIL, "P13 latency baseline (verdict=%s)", p_name); DBG_INFO("[LAT] report complete"); TEST_REPORT("Phase 13"); s_p13_clean_done = 1U; /* 已下发 0xFF:peer 正常收尾,停止周期自愈 */ s_p13_done = 1U; s_p13_state = P13_ST_ABORT; break; } default: break; } #else /* ENABLE_PHASE13_TESTS */ (void)sockfd; #endif /* ENABLE_PHASE13_TESTS */ } #endif /* ENABLE_PHASE13_TESTS */ #ifdef ENABLE_PHASE14_TESTS /* ==================================================================== * Phase 14 — 生产路径时延(L8~L11,M3) * * 与 Phase 13 的关键区别:**不占用测试 socket**,测的是真实生产路径 * (uiTask + ui_net_sink :7003 + 真实 LVGL 取帧),由 PC 端 * `latency_peer.py --mode remo --port 7003` 驱动。 * * 探针是全局单窗口 → 三个窗口必须串行: * ui_loop uiTask 主循环实测周期(本地,不需要 peer,120 样本) * prod_cycle_mcu ui_net_sink 处理一帧 LCDMEM 的 MCU 净成本 * (取 1bpp 位图 + 组帧 + net_send 队列/芯片;不含 10ms 拾取拍) * relisten peer 断开后自动重监听耗时(量化 D10:Trap 09 重试含 osDelay(500)) * * 端口分工:peer 走 :7003(生产 socket,uiTask 持有),本 Phase 不碰 :8080。 * 段阈值(首轮 M3 实测后按 §4.2 重锁): * ui_loop warn 25ms / crit 50ms * prod_cycle warn 20ms / crit 200ms * relisten warn 50ms / crit 500ms(spike ≥500ms = D10 指纹) * ==================================================================== */ #define LAT_P14_UI_SAMPLES 120U #define LAT_P14_PROD_SAMPLES 50U #define LAT_P14_REL_SAMPLES 3U /* = peer --reconnect-rounds(每次 close 触发一次 auto-relisten) */ #define LAT_P14_UI_WIN_MS 20000U #define LAT_P14_PROD_WIN_MS 90000U #define LAT_P14_REL_WIN_MS 60000U #define LAT_P14_TIMEOUT_MS 240000U #define LAT_P14_UI_WARN_US 25000U #define LAT_P14_UI_CRIT_US 50000U #define LAT_P14_PROD_WARN_US 20000U #define LAT_P14_PROD_CRIT_US 200000U #define LAT_P14_REL_WARN_US 100000U /* D10 修复后实测 89ms(重试间隔 30ms + 芯片沉降); * 原 50ms 阈值对应"旧实现 588ms"已不适用 */ #define LAT_P14_REL_CRIT_US 500000U enum { P14_ST_UI_LOOP = 0, P14_ST_PROD, P14_ST_RELISTEN, P14_ST_DONE }; static uint8_t s_p14_done = 0U; static uint8_t s_p14_banner = 0U; #if defined(TEST_LATENCY_PROBE) static uint8_t s_p14_state = (uint8_t)P14_ST_UI_LOOP; static uint8_t s_p14_win_open = 0U; static int s_p14_verdict_all = LAT_VERDICT_OK; static uint32_t s_p14_start_tick; static uint32_t s_p14_win_tick; #endif int net_latency_phase14_busy(void) { return (s_p14_done == 0U) ? 1 : 0; } #if defined(TEST_LATENCY_PROBE) /* 收尾一个窗口:disarm → 取统计 → 报告 → verdict 累计 */ static void p14_close_seg(const char *p_seg, uint32_t warn_us, uint32_t crit_us, uint16_t max_spikes) { const lat_stats_t *p_st; int v = LAT_VERDICT_OK; lat_probe_disarm(); p_st = lat_probe_stats(); if (p_st->n == 0U) { DBG_INFO("[LAT] note: %s window got 0 samples (peer missing/timeout)", p_seg); v = LAT_VERDICT_FAIL; } else if (p_st->med == 0U) { /* 中位数为 0 = 计时器未使能/探针未生效(真实处理不可能 0µs)。 * 2026-09-22 run16 实证:本 Phase 曾漏调 lat_time_init() → DWT CYCCNT * 恒 0 → 三段全 0 却输出"verdict=OK"的假绿 → 必须判无效。 */ DBG_ERROR("[LAT] note: %s med=0us -> probe timer not armed (result invalid)", p_seg); v = LAT_VERDICT_FAIL; } else { lat_stat_report(p_seg, p_st, warn_us, crit_us, max_spikes, &v); } if (v > s_p14_verdict_all) { s_p14_verdict_all = v; } } #endif /* TEST_LATENCY_PROBE */ void net_latency_phase14_run(void) { #if defined(TEST_LATENCY_PROBE) if (s_p14_done != 0U) { return; } /* 一次性横幅 + 操作提示 */ if (s_p14_banner == 0U) { s_p14_banner = 1U; s_p14_start_tick = HAL_GetTick(); lat_time_init(); /* DWT CYCCNT 使能(漏调则所有样本恒 0) */ DBG_INFO("=== CH395F Phase 14 Tests (prod-path latency, L8~L11) ==="); DBG_INFO("[LAT] run_id=%lu phase=14 cal core_clk=%lu us_per_1024cyc=%lu", (unsigned long)s_p14_start_tick, (unsigned long)SystemCoreClock, (unsigned long)(((uint64_t)1024U * 1000000UL) / (uint64_t)SystemCoreClock)); DBG_INFO("[LAT] prod path: uiTask + ui_net_sink :7003 " "(run latency_peer.py --mode remo --port 7003)"); } /* 整体超时保护:避免 peer 缺席时无限等窗口 */ if ((HAL_GetTick() - s_p14_start_tick) > LAT_P14_TIMEOUT_MS) { DBG_ERROR("[LAT] phase14 abort: overall timeout"); s_p14_done = 1U; return; } switch (s_p14_state) { case P14_ST_UI_LOOP: if (s_p14_win_open == 0U) { lat_probe_arm("ui_loop", LAT_P14_UI_SAMPLES, 0U); s_p14_win_tick = HAL_GetTick(); s_p14_win_open = 1U; break; } if ((lat_probe_count() >= LAT_P14_UI_SAMPLES) || ((HAL_GetTick() - s_p14_win_tick) > LAT_P14_UI_WIN_MS)) { p14_close_seg("ui_loop", LAT_P14_UI_WARN_US, LAT_P14_UI_CRIT_US, 0U); s_p14_state = (uint8_t)P14_ST_PROD; s_p14_win_open = 0U; } break; case P14_ST_PROD: if (s_p14_win_open == 0U) { lat_probe_arm("prod_cycle_mcu", LAT_P14_PROD_SAMPLES, 500000U); s_p14_win_tick = HAL_GetTick(); s_p14_win_open = 1U; break; } if ((lat_probe_count() >= LAT_P14_PROD_SAMPLES) || ((HAL_GetTick() - s_p14_win_tick) > LAT_P14_PROD_WIN_MS)) { p14_close_seg("prod_cycle_mcu", LAT_P14_PROD_WARN_US, LAT_P14_PROD_CRIT_US, 0U); s_p14_state = (uint8_t)P14_ST_RELISTEN; s_p14_win_open = 0U; } break; case P14_ST_RELISTEN: if (s_p14_win_open == 0U) { lat_probe_arm("relisten", LAT_P14_REL_SAMPLES, LAT_P14_REL_CRIT_US); s_p14_win_tick = HAL_GetTick(); s_p14_win_open = 1U; break; } if ((lat_probe_count() >= LAT_P14_REL_SAMPLES) || ((HAL_GetTick() - s_p14_win_tick) > LAT_P14_REL_WIN_MS)) { p14_close_seg("relisten", LAT_P14_REL_WARN_US, LAT_P14_REL_CRIT_US, 1U); s_p14_state = (uint8_t)P14_ST_DONE; s_p14_win_open = 0U; } break; case P14_ST_DONE: default: { const char *p_name = (s_p14_verdict_all == LAT_VERDICT_OK) ? "OK" : ((s_p14_verdict_all == LAT_VERDICT_WARN) ? "WARN" : "FAIL"); DBG_INFO("[LAT] summary verdict=%s", p_name); TEST_CHECK(s_p14_verdict_all != LAT_VERDICT_FAIL, "P14 prod-path latency baseline (verdict=%s)", p_name); DBG_INFO("[LAT] report complete"); TEST_REPORT("Phase 14"); s_p14_done = 1U; break; } } #else /* TEST_LATENCY_PROBE */ if (s_p14_banner == 0U) { s_p14_banner = 1U; DBG_INFO("[LAT] phase14 skipped: TEST_LATENCY_PROBE not compiled"); s_p14_done = 1U; } #endif /* TEST_LATENCY_PROBE */ } #endif /* ENABLE_PHASE14_TESTS */ #endif /* ENABLE_PHASE12/13/14 任一使能 */