Files
STM32F4-Base/test/board/net_latency_test.c
T

1345 lines
51 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* 模块名称: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 <string.h>
#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;
}
/* 段名比较(避免为一处探针引入 <string.h> 依赖) */
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 §3):
* 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 §6.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
* 静默档,见 §6.2 M2 基线) */
#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 任一使能 */