f754d59218
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/, 顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。 - 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变) - 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts - 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob (分层后不递归扫描=追溯门禁直接瞎掉) - scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError, 让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把 stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法 - docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref 搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链 不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原) - docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的 「启动日志原子性复验流程」一行 - .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新, 该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线 (实测重建后仅 watchdog-module 一条哈希变化) - 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与 .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行), 任务表块与钩子声明一字未动 验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿; check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增 CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
1313 lines
51 KiB
C
1313 lines
51 KiB
C
/******************************************************************************
|
||
* 模块名称:CH395F 链路延迟测试(实现)
|
||
* 模块功能:DWT CYCCNT 微秒时基 + 采样统计(min/med/p95/max/尖峰)+ [LAT]
|
||
* 机器可读报告与 verdict;Phase 12(M1):SPI 字节命令 / DMA 时延
|
||
* 基线(SPI_CMD/SPI_DMA,硬件层模式,无需 PC)。分段定义与判定基线见
|
||
* docs/测试说明/专项验证/链路延迟测试说明.md。
|
||
* 适用平台:STM32F4 系列(CH395F)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-22
|
||
* 修改记录:
|
||
* 2026-09-22 王建锋 初版:时基 + 统计/报告 + Phase 12(SPI_CMD/SPI_DMA)
|
||
*
|
||
* 构建形态:
|
||
* - 产品构建:三个 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(SPI_CMD 字节命令 / SPI_DMA 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 的段
|
||
* (TO_PC/PROD_CYCLE 整帧与重传尖峰)由 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 的 CHUNK_WAIT 探针经此访问 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 报告段内
|
||
* 打点(CHUNK_WAIT 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)
|
||
{
|
||
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 不写入 */
|
||
/* 有界拷贝:先取源串实际长度(全部调用点均为字符串字面量),再夹到目标容量
|
||
* 以内。原实现"按目标容量逐字节读源 + 遇 '\0' break"虽对字面量安全,但
|
||
* cppcheck 无法证明源串提前终止 → 报 arrayIndexOutOfBounds(S5,2026-10-03);
|
||
* 改为 strlen+memcpy 后既有界又可证。 */
|
||
{
|
||
size_t n = strlen(seg);
|
||
|
||
if (n > sizeof(s_probe_seg) - 1U) {
|
||
n = sizeof(s_probe_seg) - 1U;
|
||
}
|
||
memcpy(s_probe_seg, seg, n);
|
||
s_probe_seg[n] = '\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 时延基线(SPI_CMD/SPI_DMA,硬件层模式,无需 PC)
|
||
*
|
||
* 测量对象(见 链路延迟测试说明.md §3):
|
||
* SPI_CMD SPI 字节命令 —— 3 个代表性命令的完整 SPI 事务时长
|
||
* (公开函数内部含 spi_begin/spi_end,即一次完整事务)
|
||
* SPI_DMA 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 /* SPI_CMD 每命令样本数 */
|
||
#define LAT_P12_DMA_N 100U /* SPI_DMA 每长度档样本数 */
|
||
#define LAT_P12_DMA_LEN_MAX 2048U /* SPI_DMA 单发上限 = CH395F_SPI_DMA_MAX_PAYLOAD */
|
||
|
||
/* SPI_CMD 阈值(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, SPI_CMD/SPI_DMA) ===");
|
||
|
||
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));
|
||
|
||
/* ---- SPI_CMD: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:
|
||
/* 例外(同 Phase 1 全局中断状态用例):被测对象就是本 API 的 SPI 时延——硬件层
|
||
* 独占上下文、无并发消费者;除此之外测试代码禁读全局寄存器
|
||
* (读即清,唯一读者 = net_poll,唯一读者红线) */
|
||
(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;
|
||
}
|
||
}
|
||
|
||
/* ---- SPI_DMA: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 — 芯片 + 队列时延(CHUNK_WAIT~FRAME_RT/LOGTAX,需 PC peer,M2)
|
||
*
|
||
* 由 net_layer_test_main() 监听循环(8080 端口,~10ms 节拍)事件驱动。
|
||
* 服务器驱动协议:MCU 发操作字节,peer(test/host/latency_peer.py --mode net)
|
||
* 执行对应流量;段完成后 MCU 打 [LAT] 报告行并推进下一操作。
|
||
*
|
||
* 段定义(说明文档 §1):
|
||
* QUEUE_RT queue_rt_recv / queue_rt_send —— 调用方视角 net_recv/net_send
|
||
* 全程(入队 + netTask 调度 + 处理 + 通知),静默基线档;
|
||
* 压力档 = peer --ftp-stress(lftpd 阻塞读占住 netTask → 头阻塞复现)
|
||
* ECHO_RT req2first_mcu —— 请求收齐 → 应答发送完成(MCU 侧,含队列+1 chunk)
|
||
* FRAME_RT fullframe_mcu —— 9B LCDMEM 请求 → 3209B 全帧发送完成
|
||
* (netTask 内 4 次分块停止等待,= 生产路径发送段)
|
||
* CHUNK_WAIT chunk_wait —— netTask 内"写发送缓冲 → SENBUF_FREE"等待
|
||
* (调用方不可见,经 TEST_LATENCY_PROBE 探针,见 net_socket.c)
|
||
* LOGTAX logtax_line —— 一行 60 字符 printf(fputc 逐字节 @115200)
|
||
*
|
||
* 执行顺序(2026-09-22 run3 后重排):QUEUE_RT → CHUNK_WAIT → LOGTAX → ECHO_RT → FRAME_RT ——
|
||
* 失步敏感的小帧 RX 协议段(ECHO_RT/FRAME_RT)放最后,先锁住抗失步段的基线。
|
||
* 对端侧(TO_PC 线上传输/整帧到达/尖峰)由 peer JSON 报告,按主机时间戳关联。
|
||
*
|
||
* 阈值:初始值,台架首跑后重锁(说明文档 §4.2)。
|
||
* 独立运行:与其他 Phase 共用 8080 socket,混跑会污染帧测量。
|
||
* ==================================================================== */
|
||
|
||
#define LAT_P13_SAMPLES 10U /* ECHO_RT/FRAME_RT 每段样本数(2026-09-22 run3 实证:ECHO_RT 50 轮
|
||
* 小交换后芯片 TCP 失步 → TX 停滞 + 重复递交 9B 碎片
|
||
* → 降至 10,先取干净基线再量化失步边界) */
|
||
#define LAT_P13_QUEUE_RT_CHUNKS 20U /* QUEUE_RT flood 块数(2026-09-22 run1 实证:50×100B@5ms
|
||
* 达 20KB/s 饱和芯片接收缓冲 → 窗口关闭重传 →
|
||
* 重复递交 11884/5000B → 对端 RST;降至 1KB/s
|
||
* 静默档,见 §6.2 M2 基线) */
|
||
#define LAT_P13_QUEUE_RT_CHUNK_LEN 100U
|
||
#define LAT_P13_CHUNK_WAIT_SENDS 16U /* 每次 4KB = 4 chunk → 16×4 = 64 探针样本
|
||
* (run3 后从 32 减半:控制持续 TX 总量) */
|
||
#define LAT_P13_CHUNK_WAIT_SEND_LEN 4096U
|
||
#define LAT_P13_CHUNK_WAIT_SAMPLES (LAT_P13_CHUNK_WAIT_SENDS * 4U)
|
||
#define LAT_P13_ECHO_RT_SIZE 10U
|
||
#define LAT_P13_FRAME_RT_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_QUEUE_RT_WARN_US 10000U
|
||
#define LAT_P13_QUEUE_RT_CRIT_US 100000U
|
||
#define LAT_P13_REQ2FIRST_WARN_US 20000U
|
||
#define LAT_P13_REQ2FIRST_CRIT_US 200000U
|
||
#define LAT_P13_FULLFRAME_WARN_US 50000U
|
||
#define LAT_P13_FULLFRAME_CRIT_US 500000U
|
||
#define LAT_P13_CHUNK_WAIT_WARN_US 10000U
|
||
#define LAT_P13_CHUNK_WAIT_CRIT_US 500000U
|
||
#define LAT_P13_CHUNK_WAIT_SPIKE_US 500000U /* ≥500ms = 重传粒度指纹(D1) */
|
||
#define LAT_P13_LOGTAX_WARN_US 10000U
|
||
#define LAT_P13_LOGTAX_CRIT_US 25000U
|
||
|
||
/* 操作字节(MCU → peer) */
|
||
#define LAT_P13_OP_QUEUE_RT_RECV 0x01U
|
||
#define LAT_P13_OP_QUEUE_RT_SEND 0x02U
|
||
#define LAT_P13_OP_ECHO_RT 0x03U
|
||
#define LAT_P13_OP_FRAME_RT 0x04U
|
||
#define LAT_P13_OP_CHUNK_WAIT 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_QUEUE_RT_RECV,
|
||
P13_ST_QUEUE_RT_SEND,
|
||
P13_ST_ECHO_RT,
|
||
P13_ST_FRAME_RT,
|
||
P13_ST_CHUNK_WAIT,
|
||
P13_ST_LOGTAX,
|
||
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_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; /* ECHO_RT/FRAME_RT 单样本等待计时 */
|
||
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_CHUNK_WAIT_SEND_LEN]; /* QUEUE_RT/CHUNK_WAIT flood 载荷(模式预填) */
|
||
static uint8_t s_p13_frame[LAT_P13_FRAME_LEN]; /* FRAME_RT 应答帧(建一次) */
|
||
static uint8_t s_p13_l6rx[LAT_P13_ECHO_RT_SIZE];
|
||
static uint8_t s_p13_l7rx[LAT_P13_FRAME_RT_REQ_LEN];
|
||
|
||
/* 构造 FRAME_RT 应答帧(严格照生产 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 进 QUEUE_RT */
|
||
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_QUEUE_RT_RECV;
|
||
(void)p13_send_op(sockfd, LAT_P13_OP_QUEUE_RT_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 覆盖为 QUEUE_RT_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_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:会话终态,不再处理 */
|
||
}
|
||
/* 冗余 API 面清理(docs/网络/网络自愈架构设计.md §7):原"进 abort 分状态手动重同步 + 10s 周期 relisten"
|
||
* 已删——三种去向全部由 net 层自动机制认领(静默回收滞留 ESTABLISHED
|
||
* → 对账⑤ RESYNC;CLOSED 失位 → 对账③④ 认领重建;LISTENING 静默丢
|
||
* 监听 → 形态③"芯片不在听"判据),手动 API net_resync_listen/net_relisten
|
||
* 已退役。此处只保留收包与 peer re-HELLO 探测。 */
|
||
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 已消耗 → 直接进 QUEUE_RT 并回 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_CHUNK_WAIT_SEND_LEN; i++) {
|
||
s_p13_tx[i] = (uint8_t)(i & 0xFFU);
|
||
}
|
||
DBG_INFO("=== CH395F Phase 13 Tests (chip+queue latency, CHUNK_WAIT~FRAME_RT/LOGTAX) ===");
|
||
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;
|
||
}
|
||
|
||
/* ---- QUEUE_RT 静默基线:peer flood 50×100B,MCU 逐次 DONTWAIT 收取 ----
|
||
* 仅对"收到数据"的调用计时(队列往返 + netTask SPI 读);空轮询不采样 */
|
||
case P13_ST_QUEUE_RT_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 (QUEUE_RT 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_QUEUE_RT_CHUNKS * LAT_P13_QUEUE_RT_CHUNK_LEN) + (2U * LAT_P13_RECV_LEN)) {
|
||
p13_abort("QUEUE_RT duplicate delivery (CH395F TCP retransmit re-delivery)");
|
||
return;
|
||
}
|
||
if (s_p13_rx_bytes >= (LAT_P13_QUEUE_RT_CHUNKS * LAT_P13_QUEUE_RT_CHUNK_LEN)) {
|
||
p13_report_seg("queue_rt_recv", LAT_P13_QUEUE_RT_WARN_US, LAT_P13_QUEUE_RT_CRIT_US, 0U);
|
||
s_p13_state = P13_ST_QUEUE_RT_SEND;
|
||
s_p13_stage = 0U;
|
||
lat_stat_reset(&s_p13_st);
|
||
if (p13_send_op(sockfd, LAT_P13_OP_QUEUE_RT_SEND) == 0U) {
|
||
p13_abort("QUEUE_RT->QUEUE_RT_SEND op send failed (peer desync)");
|
||
return;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
/* ---- QUEUE_RT 静默基线:MCU 阻塞 net_send 50×100B(调用即含队列往返全程) ----
|
||
* 阻塞调用经 xTaskNotifyWait 让出 → netTask(Normal) 自然抢占,无额外延迟 */
|
||
case P13_ST_QUEUE_RT_SEND: {
|
||
while (s_p13_stage < LAT_P13_QUEUE_RT_CHUNKS) {
|
||
c0 = lat_cycles_now();
|
||
n = net_send(sockfd, s_p13_tx, (int32_t)LAT_P13_QUEUE_RT_CHUNK_LEN, 0);
|
||
us = lat_cycles_diff_us(c0);
|
||
if (n != (int32_t)LAT_P13_QUEUE_RT_CHUNK_LEN) {
|
||
s_p13_seg_force_fail = 1;
|
||
#if defined(TEST_LATENCY_PROBE)
|
||
DBG_INFO("[LAT] QUEUE_RT 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_QUEUE_RT_WARN_US, LAT_P13_QUEUE_RT_CRIT_US, 0U);
|
||
/* run3 重排:先 CHUNK_WAIT(纯 TX flood,1s 超时保护下不挂死)+ LOGTAX(本地),
|
||
* 后 ECHO_RT/FRAME_RT(RX 协议段;run3 实证芯片 TCP 对小帧交换易失步 → 靠后并降样本数) */
|
||
s_p13_state = P13_ST_CHUNK_WAIT;
|
||
s_p13_stage = 0U;
|
||
break;
|
||
}
|
||
|
||
/* ---- ECHO_RT:每样本一轮 op→10B 请求→10B 回显(MCU 侧:收齐→发出) ---- */
|
||
case P13_ST_ECHO_RT: {
|
||
n = net_recv(sockfd, s_p13_l6rx + s_p13_l6rx_n,
|
||
(int)(LAT_P13_ECHO_RT_SIZE - s_p13_l6rx_n), NET_MSG_DONTWAIT);
|
||
if (n == 0) {
|
||
p13_abort("peer connection closed (ECHO_RT)");
|
||
return;
|
||
}
|
||
if (n > 0) {
|
||
s_p13_l6rx_n += (uint16_t)n;
|
||
}
|
||
if (s_p13_l6rx_n >= LAT_P13_ECHO_RT_SIZE) {
|
||
c0 = lat_cycles_now();
|
||
n = net_send(sockfd, s_p13_l6rx, (int32_t)LAT_P13_ECHO_RT_SIZE, 0); /* 回显 */
|
||
us = lat_cycles_diff_us(c0);
|
||
if (n != (int32_t)LAT_P13_ECHO_RT_SIZE) {
|
||
s_p13_seg_force_fail = 1;
|
||
#if defined(TEST_LATENCY_PROBE)
|
||
DBG_INFO("[LAT] ECHO_RT 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_REQ2FIRST_WARN_US, LAT_P13_REQ2FIRST_CRIT_US, 0U);
|
||
s_p13_state = P13_ST_FRAME_RT;
|
||
s_p13_stage = 0U;
|
||
s_p13_l7rx_n = 0U;
|
||
s_p13_step_tick = HAL_GetTick();
|
||
(void)p13_send_op(sockfd, LAT_P13_OP_FRAME_RT);
|
||
} else {
|
||
s_p13_l6rx_n = 0U;
|
||
s_p13_step_tick = HAL_GetTick();
|
||
(void)p13_send_op(sockfd, LAT_P13_OP_ECHO_RT);
|
||
}
|
||
} else if ((HAL_GetTick() - s_p13_step_tick) > LAT_P13_SAMPLE_T_MS) {
|
||
p13_abort("ECHO_RT peer timeout");
|
||
return;
|
||
}
|
||
break;
|
||
}
|
||
|
||
/* ---- FRAME_RT:9B LCDMEM 请求 → 3209B 全帧(netTask 内 4 分块停止等待) ---- */
|
||
case P13_ST_FRAME_RT: {
|
||
n = net_recv(sockfd, s_p13_l7rx + s_p13_l7rx_n,
|
||
(int)(LAT_P13_FRAME_RT_REQ_LEN - s_p13_l7rx_n), NET_MSG_DONTWAIT);
|
||
if (n == 0) {
|
||
p13_abort("peer connection closed (FRAME_RT)");
|
||
return;
|
||
}
|
||
if (n > 0) {
|
||
s_p13_l7rx_n += (uint16_t)n;
|
||
}
|
||
if (s_p13_l7rx_n >= LAT_P13_FRAME_RT_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("FRAME_RT 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_FULLFRAME_WARN_US, LAT_P13_FULLFRAME_CRIT_US, 0U);
|
||
/* FRAME_RT 为末段(run3 重排后的最终顺序:QUEUE_RT→CHUNK_WAIT→LOGTAX→ECHO_RT→FRAME_RT→DONE);
|
||
* 此处若回跳 CHUNK_WAIT 会形成 CHUNK_WAIT→LOGTAX→ECHO_RT→FRAME_RT 无限循环、永不下发 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_FRAME_RT);
|
||
}
|
||
} else if ((HAL_GetTick() - s_p13_step_tick) > LAT_P13_SAMPLE_T_MS) {
|
||
p13_abort("FRAME_RT peer timeout");
|
||
return;
|
||
}
|
||
break;
|
||
}
|
||
|
||
#if defined(TEST_LATENCY_PROBE)
|
||
/* ---- CHUNK_WAIT:探针武装 + 32×4KB 发送 flood(= 128 chunk 样本) ----
|
||
* chunk_wait 在 netTask 内测量(写发送缓冲 → SENBUF_FREE),
|
||
* 调用方侧不可见 → 必须经 net_socket.c 探针(TEST_LATENCY_PROBE) */
|
||
case P13_ST_CHUNK_WAIT: {
|
||
if (s_p13_stage == 0U) {
|
||
(void)p13_send_op(sockfd, LAT_P13_OP_CHUNK_WAIT);
|
||
lat_probe_arm("chunk_wait", (uint32_t)LAT_P13_CHUNK_WAIT_SAMPLES, LAT_P13_CHUNK_WAIT_SPIKE_US);
|
||
}
|
||
while (s_p13_stage < LAT_P13_CHUNK_WAIT_SENDS) {
|
||
n = net_send(sockfd, s_p13_tx, (int32_t)LAT_P13_CHUNK_WAIT_SEND_LEN, 0);
|
||
if (n != (int32_t)LAT_P13_CHUNK_WAIT_SEND_LEN) {
|
||
s_p13_seg_force_fail = 1;
|
||
#if defined(TEST_LATENCY_PROBE)
|
||
DBG_INFO("[LAT] CHUNK_WAIT 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_CHUNK_WAIT_WARN_US,
|
||
LAT_P13_CHUNK_WAIT_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_CHUNK_WAIT_SAMPLES);
|
||
}
|
||
s_p13_seg_force_fail = 0;
|
||
s_p13_state = P13_ST_LOGTAX;
|
||
s_p13_stage = 0U;
|
||
}
|
||
break;
|
||
}
|
||
#else /* TEST_LATENCY_PROBE */
|
||
case P13_ST_CHUNK_WAIT: {
|
||
/* 探针未编译(未选 Phase 13/14 之外的组合):跳过本段 */
|
||
DBG_INFO("[LAT] skip chunk_wait (probe not compiled)");
|
||
s_p13_state = P13_ST_LOGTAX;
|
||
s_p13_stage = 0U;
|
||
break;
|
||
}
|
||
#endif /* TEST_LATENCY_PROBE */
|
||
|
||
/* ---- LOGTAX:一行 60 字符 printf 的耗时(fputc 逐字节 @115200,D6 量化) ---- */
|
||
case P13_ST_LOGTAX: {
|
||
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_LOGTAX_WARN_US, LAT_P13_LOGTAX_CRIT_US, 0U);
|
||
/* ECHO_RT 前清空 socket 残留(重传/重复递交字节,run1 实证 6884B、run3 实证 9B 碎片):
|
||
* 残留会与 ECHO_RT/FRAME_RT 的请求帧错位混读 → 协议失步。此窗口对端静默
|
||
* (对端只在收到操作字节后才发包),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 ECHO_RT (retransmit/duplicate)",
|
||
(unsigned)drained);
|
||
}
|
||
}
|
||
s_p13_state = P13_ST_ECHO_RT;
|
||
s_p13_stage = 0U;
|
||
s_p13_l6rx_n = 0U;
|
||
s_p13_step_tick = HAL_GetTick();
|
||
(void)p13_send_op(sockfd, LAT_P13_OP_ECHO_RT);
|
||
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_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 — 生产路径时延(PROD_CYCLE~UI_LOOP,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-NET-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, PROD_CYCLE~UI_LOOP) ===");
|
||
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 任一使能 */
|