From ee206ab688deb83ae02407fd8d44b8922e7a2c18 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 21 Sep 2026 16:55:05 +0800 Subject: [PATCH] =?UTF-8?q?GD5F=E9=A1=B5=E8=AF=BB=E5=9B=9E=E5=BD=92?= =?UTF-8?q?=E4=B8=BB=E7=BA=BF=E5=8D=95=E5=8F=91=E4=BB=A4(settle=E4=B8=8E?= =?UTF-8?q?=E4=BA=8C=E6=AC=A1=E5=8F=91=E4=BB=A4=E7=A7=BB=E9=99=A4=E3=80=81?= =?UTF-8?q?3.1V=E8=AF=AF=E5=88=A4=E6=9B=B4=E6=AD=A3=E5=BD=92=E4=B8=80Trap0?= =?UTF-8?q?5)=E3=80=81=E6=96=B0=E5=A2=9EPhase11=E5=A4=8D=E7=8E=B0=E7=94=A8?= =?UTF-8?q?=E4=BE=8B(16=E8=BD=AE=E9=9B=B6=E5=A4=8D=E7=8E=B0)=E3=80=81?= =?UTF-8?q?=E9=99=84=E5=8F=B0=E6=9E=B6=E7=8E=AF=E5=A2=83=E4=B8=8E=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .opencode/serial_config.json | 4 +- Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c | 22 +- Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h | 11 +- MDK-ARM/STM32F407-Demo.uvprojx | 338 +++++++++++--- docs/测试说明/ST-LINK_CLI_复位使用说明.md | 44 +- docs/问题记录/GD5F2GQ5UE_Trap_Records.md | 14 +- docs/问题记录/错误码传播审计.md | 523 ++++++++++++++++++++++ test/gd5f_test.h | 2 + test/gd5f_test_task.c | 148 ++++++ test/test_config.h | 1 + 11 files changed, 965 insertions(+), 145 deletions(-) create mode 100644 docs/问题记录/错误码传播审计.md diff --git a/.gitignore b/.gitignore index 2e69cd58..08006df5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ *.map *.lnp *.dep +# Keil UV4 预处理列表文件("Generate preprocessed source file" 选项产生的构建中间物,勿入库) +*__i +*._ia *.build_log.htm *.htm *.log diff --git a/.opencode/serial_config.json b/.opencode/serial_config.json index 2574620f..a3eab05b 100644 --- a/.opencode/serial_config.json +++ b/.opencode/serial_config.json @@ -1,6 +1,6 @@ { - "portName": "COM7", + "portName": "COM6", "portHint": "USB 串行设备", "baudRate": 115200, - "note": "MCU 调试串口(USART1 → CH340 → 本机枚举为 COM7)。Python 串口助手(tools/serial_tool)与 @test 均以此文件为端口/波特率默认值(单一配置源);执行前先读取。如本机端口变化,请人工修改此文件。" + "note": "MCU 调试串口(USART1 → CH340 → 本机枚举为 COM6(2026-09-21 台架重插后由 COM7 变更))。Python 串口助手(tools/serial_tool)与 @test 均以此文件为端口/波特率默认值(单一配置源);执行前先读取。如本机端口变化,请人工修改此文件。" } diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c index 2c9d8f5e..64e7cb26 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c @@ -226,13 +226,11 @@ static void gd5f_page_read_issue(uint32_t page_addr) { * 入口参数:page_addr - 页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1 * 返回值:0 - 成功,其它 - 错误 * 限定条件:SPI 已初始化 - * 函数说明:13h 二次发令 + 固定沉降延时 GD5F_PAGE_READ_SETTLE_MS。 - * 台架实证(2026-09-20,VDD 3.1V 欠压工况):① 就绪信号可早于 - * array→buffer 整页装载完成 → 0Bh 读到"前段新、后段旧"混合页; - * ② 13h 命令在编程/擦除风暴后可不被芯片锁存(空操作)→ 0Bh - * 读到上一操作残留 buffer(编程数据)→ 后续文件/BBT/journal - * 读取全部"虚假成功",阵列实际未更新 → 复位后数据丢失。 - * 二次发令覆盖②(重发必被锁存),固定沉降覆盖①(整页装载完成)。 + * 函数说明:13h 单次发令 + WIP 轮询(与 Linux 主线 / GigaDevice 厂商驱动 + * 形状完全一致:13h×1 → OIP 轮询 → 0Bh)。 + * 2026-09-20 台架异常史实(归因、证伪、沉降/二次发令的加与除) + * 统一记于 docs/问题记录/GD5F2GQ5UE_Trap_Records.md Trap 05, + * 复现手段见测试 Phase 11 用例。 */ int gd5f_page_read(uint32_t page_addr) { int ret; @@ -243,16 +241,6 @@ int gd5f_page_read(uint32_t page_addr) { return ret; } - /* 二次发令:首条 13h 未被锁存(空操作)时由重发兜底 */ - gd5f_page_read_issue(page_addr); - ret = gd5f_wait_busy(100); - if (ret != GD5F_OK) { - return ret; - } - - /* 固定沉降:等待 array→buffer 整页装载完成 */ - HAL_Delay(GD5F_PAGE_READ_SETTLE_MS); - return GD5F_OK; } diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h index d2e82b06..884e03ad 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h @@ -60,15 +60,10 @@ extern "C" { #define GD5F_BLOCK_SIZE (GD5F_PAGES_PER_BLOCK * GD5F_PAGE_SIZE) /* - * 页读取固定沉降延时(2026-09-20 台架实证):13h 发出后 array→buffer - * 的整页装载完成前,就绪信号可能提前清零(低 VDD 下内部传输速率下降, - * 确定性早于 0Bh 读完成)→ 0Bh 流式读到"前段新、后段旧"的混合页 - * (journal checkpoint header 有效而 meta 残留旧数据 → 复位后 resume - * 锚定新 checkpoint 却读到旧树 → 数据静默丢失)。13h 之后、0Bh 之前 - * 强制等待该时长,保证整页新鲜。取值须覆盖低 VDD 下 t_RR 最慢工况, - * 同时受开机 BBT 全片扫描(2048 次页读)耗时约束:2ms 实测两可。 + * 页读取就绪判定 = WIP 轮询(与 Linux 主线 / GigaDevice 厂商驱动完全一致, + * 单发令形状)。2026-09-20 沉降 / 13h 二次发令史实统一记于 + * GD5F2GQ5UE_Trap_Records.md Trap 05。 */ -#define GD5F_PAGE_READ_SETTLE_MS 2 #define GD5F_TOTAL_BLOCKS 2048 #define GD5F_TOTAL_SIZE (GD5F_TOTAL_BLOCKS * GD5F_BLOCK_SIZE) diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index eb7ce17e..ede6b401 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -1,7 +1,10 @@ - - + + + 2.1 +
### uVision Project, (C) Keil Software
+ STM32F407-Demo @@ -16,28 +19,28 @@ Keil.STM32F4xx_DFP.3.1.1 https://www.keil.com/pack/ IRAM(0x20000000-0x2001BFFF) IRAM2(0x2001C000-0x2001FFFF) IROM(0x8000000-0x80FFFFF) CLOCK(25000000) FPU2 CPUTYPE("Cortex-M4") TZ - - - + + + 0 - - - - - - - - - - + + + + + + + + + + $$Device:STM32F407ZGTx$CMSIS\SVD\STM32F407.svd 0 0 - - - - - + + + + + 0 0 @@ -52,15 +55,15 @@ 1 1 1 - + 1 0 0 0 0 - - + + 0 0 0 @@ -69,8 +72,8 @@ 0 0 - - + + 0 0 0 @@ -79,15 +82,15 @@ 0 1 - - + + 0 0 0 0 1 - + 0 @@ -101,8 +104,8 @@ 0 0 3 - - + + 0 @@ -136,10 +139,10 @@ 1 BIN\UL2CM3.DLL "" () - - - - + + + + 0 @@ -172,7 +175,7 @@ 0 0 "Cortex-M4" - + 0 0 0 @@ -306,7 +309,7 @@ 0x0 - + 1 @@ -335,7 +338,7 @@ --diag_suppress=1,68,111,188,191,546,1293 USE_HAL_DRIVER,STM32F407xx,NDEBUG - + ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/log;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test;../Lib/lvgl;../Drivers/BSP/UI;../Lib @@ -351,9 +354,9 @@ 0 1 - - - + + + ../Drivers/CMSIS/Include @@ -364,15 +367,15 @@ 0 1 1 - - - + + + STM32F407-Demo\STM32F407-Demo.sct - - - - - + + + + + @@ -417,6 +420,8 @@ 2 2 11 + + 1 @@ -444,6 +449,12 @@ 2 2 2 + + + + + + @@ -465,6 +476,8 @@ 2 2 11 + + 1 @@ -492,6 +505,12 @@ 2 2 2 + + + + + + @@ -513,6 +532,8 @@ 2 2 11 + + 1 @@ -540,6 +561,12 @@ 2 2 2 + + + + + + @@ -561,6 +588,8 @@ 2 2 11 + + 1 @@ -588,6 +617,12 @@ 2 2 2 + + + + + + @@ -609,6 +644,8 @@ 2 2 11 + + 1 @@ -636,6 +673,12 @@ 2 2 2 + + + + + + @@ -667,6 +710,8 @@ 2 2 11 + + 1 @@ -694,6 +739,12 @@ 2 2 2 + + + + + + @@ -765,6 +816,8 @@ 2 2 11 + + 1 @@ -792,6 +845,12 @@ 2 2 2 + + + + + + @@ -813,6 +872,8 @@ 2 2 11 + + 1 @@ -840,6 +901,12 @@ 2 2 2 + + + + + + @@ -926,6 +993,8 @@ 2 2 11 + + 1 @@ -953,6 +1022,12 @@ 2 2 2 + + + + + + @@ -974,6 +1049,8 @@ 2 2 11 + + 1 @@ -1001,6 +1078,12 @@ 2 2 2 + + + + + + @@ -1022,6 +1105,8 @@ 2 2 11 + + 1 @@ -1049,6 +1134,12 @@ 2 2 2 + + + + + + @@ -1070,6 +1161,8 @@ 2 2 11 + + 1 @@ -1097,6 +1190,12 @@ 2 2 2 + + + + + + @@ -1203,6 +1302,8 @@ 2 2 11 + + 1 @@ -1230,6 +1331,12 @@ 2 2 2 + + + + + + 2 @@ -1242,6 +1349,12 @@ 2 2 1 + + + + + + @@ -1263,6 +1376,8 @@ 2 2 11 + + 1 @@ -1290,6 +1405,12 @@ 2 2 2 + + + + + + @@ -1311,6 +1432,8 @@ 2 2 11 + + 1 @@ -1338,6 +1461,12 @@ 2 2 2 + + + + + + @@ -1359,6 +1488,8 @@ 2 2 11 + + 1 @@ -1386,6 +1517,12 @@ 2 2 2 + + + + + + @@ -1407,6 +1544,8 @@ 2 2 11 + + 1 @@ -1434,6 +1573,12 @@ 2 2 2 + + + + + + @@ -1455,6 +1600,8 @@ 2 2 11 + + 1 @@ -1482,6 +1629,12 @@ 2 2 2 + + + + + + @@ -1503,6 +1656,8 @@ 2 2 11 + + 1 @@ -1530,6 +1685,12 @@ 2 2 2 + + + + + + @@ -1551,6 +1712,8 @@ 2 2 11 + + 1 @@ -1578,6 +1741,12 @@ 2 2 2 + + + + + + @@ -1599,6 +1768,8 @@ 2 2 11 + + 1 @@ -1626,6 +1797,12 @@ 2 2 2 + + + + + + @@ -1647,6 +1824,8 @@ 2 2 11 + + 1 @@ -1674,6 +1853,12 @@ 2 2 2 + + + + + + @@ -1695,6 +1880,8 @@ 2 2 11 + + 1 @@ -1722,6 +1909,12 @@ 2 2 2 + + + + + + @@ -1743,8 +1936,8 @@ 2 2 11 - - + + 1 @@ -1774,9 +1967,9 @@ 2 --diag_suppress=177 - - - + + + @@ -1791,10 +1984,10 @@ 2 1 - - - - + + + + @@ -1877,8 +2070,8 @@ 0 0 3 - - + + 0 @@ -1907,10 +2100,10 @@ 2 2 - - - - + + + + @@ -1925,10 +2118,10 @@ 2 0 - - - - + + + + @@ -3037,18 +3230,20 @@ + - + - + - + - + + @@ -3057,4 +3252,5 @@ +
diff --git a/docs/测试说明/ST-LINK_CLI_复位使用说明.md b/docs/测试说明/ST-LINK_CLI_复位使用说明.md index c213c196..11c07829 100644 --- a/docs/测试说明/ST-LINK_CLI_复位使用说明.md +++ b/docs/测试说明/ST-LINK_CLI_复位使用说明.md @@ -155,7 +155,7 @@ ST-LINK_CLI -c SWD HOTPLUG -Dump 20000000 1000000 dump.bin :: 转储 SRAM | `-P <文件> [地址] [ske] [skpv]` | 编程(ske=跳擦除 skpv=跳校验) | | `-V [after_programming]` | 校验 | | `-ME` / `-SE <扇区>` | 全片擦除 / 扇区擦除(⚠️ RDP≠0 时 `-ME` 会被拒) | -| `-TVolt` | 目标 VDD 电压 | +| `-TVolt` | 目标 VDD 电压(⚠️ 需 VDD 感应脚接至目标;本机台架 3 线连接(SWDIO/SWCLK/GND)此脚未接,读数无效,供电判据以万用表实测为准) | | `-Q` / `-Log` / `-NoPrompt` | 静默 / 生成跟踪日志 / 免交互确认 | | `-List` | 枚举所有已连接探针(固件版本 + 串号) | @@ -192,7 +192,7 @@ ST-LINK_CLI -c SWD -P "MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex" -Rst -Run ⓞ 注意:`UR`(复位态连接)在本机不可用(nRST 未接线)——跑飞救援能力受限,能做的只有"正常连接 + LPM + 全擦重烧";若板子连 SWD 都握不上手,需先补 nRST 线。 -**D. 运行中检查**:`-c SWD HOTPLUG -SCore` / `-R32` / `-TVolt` / `-Dump`。 +**D. 运行中检查**:`-c SWD HOTPLUG -SCore` / `-R32` / `-TVolt`(⚠️ 本机 3 线连接下读数无效,见命令表注记)/ `-Dump`。 ## 6. `reset.bat`(已落工程 `MDK-ARM\reset.bat`,2026-09-19 上板验证) @@ -226,43 +226,3 @@ ST-LINK_CLI -c SWD -P "MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex" -Rst -Run | SWD 通信不稳 | 不传 `SWCLK` 用默认时钟(本机实测 4MHz);检查长线/夹子接触 | | 误改 option bytes | `-rOB` 读回现状;`-OB RDP=0` 等回写后需再复位生效;**RDP=2 会锁死调试+全片擦除,操作前务必确认** | | 多板混淆 | `reset.bat list` 拿串号,`-c SN=<串号>` 指定 | - -## 8. 与 J-Link 命令的对照(备查) - -| 操作 | ST-LINK_CLI(本机已装) | JLinkExe(SEGGER,未安装) | -|---|---|---| -| 复位并运行 | `-c SWD -Rst -Run` | `-device STM32F407xG -if SWD -speed 4000 -reset -go` | -| 复位停核 | `-c SWD -Rst -Halt`(ⓞ 必须显式 `-Halt`) | `... -resetHalting` | -| nRST 脉冲 | ❌ 本机不可用(nRST 未接线 + v3.5.0 不支持 `RM=`) | 需 nRST 接线 + J-Link 引脚控制脚本 | -| 烧写并运行 | `-c SWD -P x.hex -Rst -Run`(实测 6.5s/1MB) | `-loadfile x.axf -reset -go` | -| 运行中检查 | `-c SWD HOTPLUG -SCore` | 需先 `-noReset` 连接或用 GDBServer | - -## 9. 上板实测记录(2026-09-19) - -- 目标:STM32F407ZGTx(CPUID 0x413,Flash 1MB),V2 CPU 板 -- 探针:ST-LINK/V2,FW **V2J37S7**,SN `0B250D12A218303030303032`;**接线 3 根(SWDIO/SWCLK/GND),nRST 未接** -- VDD 实测 **3.136V**(偏低,正常 3.3V——当前走 PC 端口供电,长期/大电流场景建议改 5V 供电) -- 串口 **COM7** @115200(板子重插后重新枚举,`serial_config.json` 已同步) -- 板内固件:测试前半段为较早的 ui-research 线构建(引脚集未知);13:59 起为 **v2-on-ui 构建(ui-research + V2 引脚补丁,10:03 编译)** - -| # | 时间 | 命令 | 结果 | -|---|---|---|---| -| 1 | 13:40 | `-List` | ✅ 识别探针(V2 / V2J37S7 / SN 如上) | -| 2 | 13:41 | `-c SWD HOTPLUG -SCore` | ✅ `Core is Running`(连接不扰运行中目标) | -| 3 | 13:41 | `-c SWD HOTPLUG -R32 0x08000000 4` | ✅ 首字=0x2001E8B8(192K SRAM 的 IRAM2 区)→ Flash 程序有效 | -| 4 | 13:41 | `-c SWD HOTPLUG -TVolt` | ✅ 3.136V | -| 5 | 13:43~13:55 | `-c SWD -Rst -Run`(多次,含 `reset.bat` 默认分支) | ✅ 每次均重启成功:`App init OK` / `Network Init` / `net init: OK` 齐备,PHY 100M FULL,UI port/menu init OK,0 条 FAIL/ERROR/HardFault | -| 6 | 13:44 | `-c SWD -Rst -SCore` | ⓞ 复位后核为 `Running`(不停核)→ 停核必须加 `-Halt` | -| 7 | 13:44 | `-c SWD -Rst -Halt -SCore -CoreReg` | ✅ 停核:SP=0x2001E8B8、PC=0x08004664(启动代码中段)、xPSR=0x21000000 | -| 8 | 13:45 | `-c SWD RM=Hrst -HardRst PULSE=100`(4 种语法排列) | ❌ v3.5.0.0 全部拒 `RM=`(`Unknown debug protocol or option`)→ CLI 层无 nRST 能力(且本机 nRST 未接线) | -| 9 | 13:4x | `reset.bat list / (默认)/ hard / halt / run` | ✅ 全分支 exit=0;`hard` 分支按设计回退软件复位(RESET_NOTE);`halt` 断开后核自动恢复运行 | -| 10 | 13:59 | `reset.bat flash`(= `-P -Rst -Run`) | ✅ `.hex` 直接支持:**1MB 编程 6.5s**,`Programming Complete` → `MCU Reset` → `Application started`,exit=0 | -| 11 | 13:59 | 烧录后启动观察 | ⚠️ **V2 引脚固件启动异常**:日志到 `[APP] RS485 init: OK` 后中断(无 GD5F/TPAFE/存储/UI 后续日志,无二次重启);核状态回读:`Core is Running`,**PC=0x08000FA0**(启动区死循环,LR=0x08000FA5,SP 正常推进)→ 根因 2026-09-20 闭环:芯片上是合并前旧向量表镜像(DMA2_Stream3 中断落空槽 → `B .` 死循环,PC=0x08000FA0 即该死循环),且此后所有"重下载"均静默失败(芯片从未更新)→ 详见 §10 | - -**结论**: - -1. **命令行复位成立**:`ST-LINK_CLI -c SWD -Rst -Run` = 板子重启(多次实测全部成功、开机日志完整)——这就是"命令控制板子重启",3 线接线完全够用。 -2. **本机复位能力 = 软件系统复位**:nRST 未接线 + v3.5.0 CLI 不支持 `RM=`,nRST 引脚级操作不可用(`reset.bat hard` 自动回退,不影响脚本闭环);需要时补 nRST 线 + 升级工具链。 -3. 停核 = `-Rst -Halt`(停点不保证是复位向量,实测 0x08004664);一次性连接语义——断开即恢复运行。 -4. **烧录路径打通**:`-P ` 实测可用(1MB ≈ 6.5s,比 UV4 快得多),`reset.bat flash` 可作为快速烧录通道。 -5. ✅ **问题已闭环(2026-09-20)**:09/19 启动卡死(PC=0x08000FA0)根因 = 芯片一直是合并前旧向量表镜像(09/19 之后所有下载均静默失败:裸 `-P` 不带 `-Rst -Run` 不落地)+ 内核启动前 `osMessageQueueNew` 代码缺陷;修复已提交(v2-on-ui `82411cf`),V0.1 板完整启动已验证(63 行日志 + 全片 `-CmpFile` 一致,见 §10)。 diff --git a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md index 9758b737..f8798548 100644 --- a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md +++ b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md @@ -160,10 +160,12 @@ if (ok) { 无 reformat 行、`[CFG] load: OK (523B)`(config.ini 跨复位存活)、VERDICT PASS。 **调查中被证伪的假设(重要反面记录)**: -1. **低 VDD"幻影编程"假设——证伪**:怀疑 3.1V 供电下 program 状态报成功 - 但 cell 未 latch。裸 NAND 持久性自检(块 2040/2041:擦→写 4 页图案→ +1. **"幻影编程"假设——证伪(低 VDD 归因同时作废)**:当时怀疑"3.1V 欠压供电"下 + program 状态报成功但 cell 未 latch。裸 NAND 持久性自检(块 2040/2041:擦→写 4 页图案→ 立即回读→跨复位再回读 seq 递增)实测 4/4 全过:芯片、驱动原语、供电 - 在同一台架上的持久性完全正常。 + 在同一台架上的持久性完全正常。**2026-09-21 更正**:实测供电为 3.3V 标称, + "3.1V 欠压"前提不成立(3.1V 数字源自 ST-LINK VDD 感应脚未接线的悬空读数), + 本假设的低 VDD 归因作废;4/4 结果本身不受影响。 2. **"journal 混合页/数据损坏"假设——误读**:所谓"LPN16 恒为 `RRaA` 垃圾、LPN0 全零、LPN15 全 FF"实为**合法卷内容**——本移植 `f_mkfs(FM_FAT32)` 创建的是 **MBR 分区卷**(分区起点 LBA 63): @@ -172,8 +174,10 @@ if (ok) { 0x41615252 "RRaA" 是合法签名!)。判定"损坏"前默认了几何=整盘 FAT32, 把合法结构当成了花屏。 3. 三个先前修复(mkfs 前清 FTL 树 ac2087e、page_read 二次发令 38b70ad、 - 读后 2ms settle)**都没有终结循环**——它们是合理硬化(全部保留), - 但不是根因。 + 读后 2ms settle dc6c206)**都没有终结循环**——它们是合理硬化, + 但不是根因。(2026-09-21:2ms settle 已移除——其"低 VDD"前提与 + "混合页"证据双双证伪,见本条 1 更正与 ST-LINK 文档命令表注记; + 二次发令同日移除——Phase 11 对照复现(16 轮编程擦除风暴 × 48 次立即/延迟整页字节比对)零复现 13h 空操作 / WIP 提前清零,页读回归主线单发令形状;mkfs 清树保留。) **定界决定性手段**: - 裸 NAND 跨复位持久性自检 → 排除介质/驱动/电源,指向文件系统层; diff --git a/docs/问题记录/错误码传播审计.md b/docs/问题记录/错误码传播审计.md new file mode 100644 index 00000000..62297a26 --- /dev/null +++ b/docs/问题记录/错误码传播审计.md @@ -0,0 +1,523 @@ +# 错误码传播审计报告(全项目) + +> **模块名称**:全项目错误码/返回值/状态传播审计 +> **文档目的**:以 2026-09-21 网络错误码事故(errno 覆盖 → 远端显示误断连)为起点,对全项目做系统性 +> 错误码传播缺陷普查:哪些机制会把"成功"传给调用方(而状态实际是失败)、哪些通道会让错误码丢失/串扰/ +> 永不可达,并按 P0/P1/P2 给出修复计划。**本文档只读审计,不修改任何代码。** +> **日期**:2026-09-21 +> **审计方法**:按 9 类缺陷模式(P1 值拷贝队列陷阱 / P2 共享全局错误变量竞态 / P3 返回值与错误码混同 / +> P4 IPC/HAL 状态未检查 / P5 事件标志语义 / P6 通知值宽度与符号 / P7 ISR 与跨上下文共享状态 / P8 外设与 +> 库特定约定 / P9 超时与错误混同)逐子系统审查;三个并行审计组(配置/日志 IPC、驱动层、net 消费方/模拟器) +> + 父审计组(net 核心/事件标志/UI/启动路径)交叉覆盖,**全部关键发现经父审计组逐行代码复核**(含芯片手册 +> 原文核对)后才入册。 +> **配套文档**:`docs/项目审计改进清单.md`(交叉引用见 §6)、`docs/问题记录/*_Trap_Records.md`(模块陷阱史)、 +> `docs/网络配置说明.md`、`AGENTS.md`(09-21 教训已固化条目)。 + +--- + +## 0. 摘要 + +**结论**:09-21 的确定性缺陷族(值拷贝回传字段、就绪位消费、EOF 关监听)已修复并在位;本次全项目普查 +**新确认 59 项**错误码传播缺陷:**P0×2、P1×22、P2×35**。2 项 P0 均为"值说成功、状态说失败"的静默 +数据丢失路径,均可在生产触发且当前零告警。(2026-09-21 复核修订:E-32 经手册 §4.8"出厂配置 82H"证伪由 P0 降 P2; +E-60"write_enable WRTC2 与手册矛盾"经三方复核(手册表头 D7~D0 顺序 + DFRobot 官方库/社区代码同序列 0x10←0x80、0x0F←0x84)证伪——0x84 本已含 D2=WRTC2,代码与手册一致,详见 §7-19) + +| 级别 | 含义 | 数量 | 编号 | +|---|---|---|---| +| P0 | 生产可触发的活跃缺陷(静默数据丢失) | 2 | E-01, E-31 | +| P1 | 故障条件下的潜在陷阱(触发需特定时序/工况,后果重) | 22 | E-02~E-10, E-33~E-45 | +| P2 | 健壮性/一致性/可维护性 | 35 | E-11~E-30, E-32, E-46~E-59 | + +### P0 速览 + +| 编号 | 位置 | 一句话 | +|---|---|---| +| E-01 | `App/cfg_file.c:1151` | `cfg_file_poll` 的"文件超长=损坏"检查是死代码(读长被钉死在阈值)→ >4096B 配置文件被截断解析当有效 → 界外配置静默回工厂默认 → 基线粘住错误状态 → 下次开机 load 守卫判"损坏" → **用户配置文件整体销毁** | +| E-31 | `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c:250-252` | `dhara_map_resume` **任何**失败(含单次瞬态页读失败)→ `dhara_map_clear` → **整卷逻辑映射静默丢弃** → FatFs 探针判卷无效 → 整卷重格式化 → 全部用户数据丢失;dhara 本可区分"真空白"(合法)与 ECC/损坏(必须上报),glue 层把错误分类全坍缩 | + +### P1 速览(22 项) + +| 编号 | 子系统 | 一句话 | +|---|---|---| +| E-02 | net 执行器 | **队列头阻塞**:任一消费者的阻塞 recv(30s)在 netTask 上下文空转 → 全体消费者的队列消息停摆最长 30s(事件仍处理);FTP 客户端空闲 >30s 即可触发 | +| E-03 | net 层 | 全局 `s_net_errno` 跨任务覆写竞态(09-21 修复的**残留**):恢复与读取之间隔任务切换,健康连接可被误拆(ui_sink :460 每 10ms 读) | +| E-04 | UI 远端推流 | `remo_send` 丢弃 `net_send` 返回值 → 3205B 响应弱网短发送**静默截断**(09-21 事故机制的调用方一侧未闭环) | +| E-05 | net 层 | 3 任务直调 `net_socket/net_bind/net_getsockname` 跨任务写 netTask 属主的 socket 表 → alloc/close 交错可双分同一 fd(SPI 铁律未破,表属主破了) | +| E-06 | lftpd | `lftpd_inet_accept_timeout`:`elapsed+=100` 计时在队列阻塞下失真(名义 5s → 实际十几分钟)+ 跨任务 state 预检误判 → PASV 偶发假 425 | +| E-07 | 存储 | `f_close`(本移植 FTL 的提交点)返回值 3 处丢弃 → 配置原子写/日志追加**假成功**(f_write 可 FR_OK 而数据仅在 cache),掉电静默丢失 | +| E-08 | 日志 | preboot 日志数组"单生产者免锁"前提在内核后不成立(队列惰性创建前,多任务并发无同步读-改-写)→ 自检记录丢失/重复 | +| E-09 | 配置 | UI 读-改-写 RAM 配置副本 × defaultTask poll 原地更新竞态 → 陈旧写回可抹掉 FTP 外部编辑(P1-3 残余,从引擎静态态转移到 RAM 层) | +| E-10 | NAND | init 单次无重试 + 内部超时/扫描失败全吞 → 冷启动瞬态 SPI 毛刺 = 好芯片判"NAND fault"整次开机无存储;反向慢芯片 = 假 OK + 垃圾坏块表 | +| E-33 | CH395F | DMA 数据路径 `void` 无错误通道:HAL 失败/1s 超时静默丢发;**收路径无条件 memcpy 陈旧 DMA 缓冲冒充新数据** | +| E-34 | CH395F | 字节级命令路径 `HAL_MAX_DELAY`(无限超时)且返回值未检查 → SPI2 状态机卡死 = netTask 永久挂死(系统无看门狗) | +| E-35 | 测试 | CH395F 套件 Phase5 块长 4093B > DMA 缓冲 2052B(MSS 2048→1024 后常量未同步)→ 整块静默丢弃,失败签名误导为 "rx stall" → 质量门禁失效 | +| E-36 | RS485 | ISR 重武装清零 `rx_size` → 文档化的 `rs485_rx_get_size` 对任务**恒返回 0**("无数据"误判族;消费者休眠中,首个接入即触发) | +| E-37 | RS485 | `rs485_init` 吞掉 RX 武装失败(`(void)`)→ UART 状态异常时 485 接收终身静默死亡 | +| E-38 | SD2506 | 负温度解码 `(int8_t)(val\|0xF0)` 在 ≤-17°C 全错(芯片用二补码;-30°C 会报 -14°C)且返回 0=OK 无指示(=既有 P2-5,本次升级 P1) | +| E-39 | SD2506 | ARST 在 CTR3(11H) bit7,驱动**从未写过 CTR3**(全库 grep 证实)→ ARST 恒为复位值 0 → `clear_alarm` 的读清除契约**永久不可达**(首次报警后 INTAF 永久锁存,API 返回 0 但什么都没清) | +| E-40 | FTL | `dhara_nand_erase/prog` 把**瞬态**(BUSY_TIMEOUT/ERROR)与单元缺陷(E_FAIL/P_FAIL)混同为坏块并持久化 → 容量永久损失(09-21 容量 95248→95200 事故的机制性同根) | +| E-41 | GD5F | `gd5f_bbt_save` 编程失败仍推进 BBT 版本并返回 OK(`void` 化 API 结构性丢失持久化失败信息)(=既有 P2-3) | +| E-42 | GD5F | `gd5f_bbt_scan` 两个读返回值全弃 → 坏块判定基于上一块/未初始化 `spare[0]`(好块判坏或坏块放行) | +| E-43 | GD5F | `gd5f2gq5ue_erase` 缺容量边界检查(read/write 有,Trap 03 文档与代码不符)→ 越界 erase 的行地址回绕可**静默擦掉块 0**(journal/BTT 所在) | +| E-44 | TPAFE | `read_all` 无任何数据有效性校验——头文件承诺的 FRSTDATA 首通道校验**未实现**(宏有定义、代码零引用)→ 芯片死亡时返回 OK + 常量垃圾 | +| E-45 | TPAFE | `s_ready` 三写者:`start_conv_irq` 触发前置 0 → 重触发即清除未消费帧(电平标志坍缩,高速采样系统性丢样) | + +--- + +## 1. 背景与方法 + +### 1.1 起点 + +2026-09-21 台架事故:`net_msg_t` 按值拷入队列 → netTask 对 `result` 字段的回写在调用方不可见 → +"错误码"读到的是栈垃圾(恒 0)→ `ui_net_sink` 把每次空轮询的 `WOULDBLOCK` 误判为真错误 → 远端连接 +建立即被 `net_close`(监听 fd 同毁,端口"连接被拒",RemoDispBus 7003 无画面)。修复(`c5a9e8c`/ +`a9aa8ce`):错误码改搭 `xTaskNotify()` 通知值回传(失败=负的具体错误码,成功=字节数/描述符),包装器 +在**调用方上下文**恢复全局 errno,成功路径不清 errno。AGENTS.md 已固化"勿给 `net_msg_t` 加回传字段"铁律。 + +本次审计回答的问题是:**同一个"值说成功、状态说失败"的缺陷族,在项目的其他子系统里还有多少活体?** + +### 1.2 缺陷模式定义(审计用分类法) + +| 模式 | 定义 | 09-21 原型 | +|---|---|---| +| P1 | 值拷贝队列陷阱:结构体按值入队,消费端字段回写对生产端不可见(指针字段安全) | `net_msg_t.result` 回传字段 | +| P2 | 共享全局错误变量跨任务竞态:写者与读者不在同一执行流,中间隔任务切换 | `s_net_errno` | +| P3 | 返回值/错误码混同:0 重载、短写、多套码系(FR_/HAL/负 errno/芯片原生码)互译缺失 | "FR_OK=成功"掩盖短写 | +| P4 | IPC/HAL 状态未检查:队列 put、HAL 调用、FTL 提交的返回值被丢弃或 `(void)` 化 | — | +| P5 | 事件标志语义错位:状态位被消费型等待清掉 / 命令位被状态型等待挂着 | `NET_READY` 被 `WaitAll` 消费 | +| P6 | 通知值宽度/符号/通道碰撞 | 通知值复用 | +| P7 | ISR/跨上下文共享状态无同步(表属主、双缓冲、重武装竞态) | — | +| P8 | 外设/库特定约定违背(写保护、提交点、DMA 缓冲契约、字面量方言) | FatFs FA_OPEN_APPEND 无 FA_WRITE | +| P9 | 超时与错误混同(超时判成功/判错误、无限超时、超时处置副作用) | — | + +### 1.3 覆盖范围与复核方式 + +| 审计组 | 范围 | 状态 | +|---|---|---| +| 父审计组 | `net_socket.c` 全文(1979 行)、`net_select.c`、`ui_task.c`、`net_task.c`、`freertos.c`、`main.c`、UI 层(`ui_port.c`/`ui_net_sink.c`/`ui_page_net_cfg.c`/`ui_page_runlog`/`ui_page_selftest`)、cmsis_os2.c 事件组实现、`lv_conf.h` | 完成(自审) | +| SA1 | `cfg_file.c`/`cfg_file_store_mcu.c` 全文、`sys_log_mcu.c`、`ringbuf.c`、`app_cfg.c`/`app_main.c` 配置段、`freertos.c` defaultTask 全路径 | 完成,关键项已复核 | +| SA2 | `ch395f.c/h`、`rs485.c/h`、`sd2506.c/h`、`gd5f2gq5ue.c/h`、`nand_ftl.c`、`tpafe5160.c/h` 全文 + `Lib/dhara`(error/journal/map)、HAL UART/SPI 错误路径、`app_main.c` 诊断段、三份 Trap_Records | 完成,关键项已复核 | +| SA3 | lftpd 5 文件、`time_sync.c`、`ui_net_sink*`、`test/net_test_task.c`、`simulator/sim/net_socket_sim.c` + 全库 grep 消费方地图 | 完成,关键项已复核 | + +复核方式:每项 P0/P1 的**核心机制**由父审计组独立读码/读手册确认后才入册(本文件 §3 各条的 +"位置"行即为复核落点);P2 条目采信审计组带行号的报告。全库 grep 核验了全部 `net_*`/`osEvent*`/ +`xTaskNotify`/`f_open|close|write`/`0xNNH` 字面量调用面。 + +--- + +## 2. 机制基线(各子系统错误码传播契约,审计基准) + +| 子系统 | 现行契约(修复后状态) | 审计基准 | +|---|---|---| +| **net 层** | 队列消息只携带**输入**(fd/长度/指针);错误码经通知值回传(负=错误码,≥0=字节数/fd);包装器仅**失败**时在调用方上下文恢复 `s_net_errno`,成功**永不**清除(`net_socket.c:1609` 文档化);`net_msg_t` 禁止加回传字段(AGENTS.md 铁律);指针字段安全(`net_accept` 地址回写为先例) | P1 族已消除;残留 = P2 全局竞态(E-03)与直调 API 的表属主(E-05) | +| **事件标志** | CMSIS 事件组 = FreeRTOS 事件组(`xEventGroupWaitBits`);**就绪位=状态位**须 `osFlagsNoClear`;**命令位**(CFG_SAVE/NET_RECONFIG)=可消费,单一属主 | 5 处屏障等待全部 NoClear 在位(§7-2);命令位消费存在非原子窗口(E-17,无载荷→自愈) | +| **配置引擎** | 静态态单属主(defaultTask);队列条目**纯输入型**(段号+路径指针,无回传字段)→ 结构性免疫 P1;CFG_SAVE = 可消费唤醒位 + `s_save_pending` volatile 锁存 + `s_save_result` 单字节状态机回传 UI | 全时序窗口(含 <500ms 双保存合并)无丢失(§7-3);RAM 副本读-改-写竞态残留(E-09) | +| **syslog** | 队列条目纯输入型(id + 静态串指针);defaultTask 唯一写者;内核前 post 走 `s_preboot[8]` 静态数组 → 队列惰性创建(首次 drain 时) | 输入型结构免疫 P1;preboot 数组内核后并发写(E-08) | +| **CH395F** | socket 命令回芯片原生 8 位状态(0x00 成功 / 0x10 BUSY 兼"进行中"与"轮询超时");init 族三值 OK/TIMEOUT/ERROR;**DMA 数据路径 `void` 无错误通道**;字节路径返回值未检查 + `HAL_MAX_DELAY` | E-33/E-34/E-46/E-47 | +| **RS485** | `HAL_StatusTypeDef` 返回;RX 数据**只能**经 `rs485_on_rx` ISR 钩子观察(getter 恒 0);init 吞武装结果 | E-36/E-37/E-48~E-50 | +| **SD2506(RTC)** | int 返回:0=OK / -1=参数 / -2=I2C;**0 仅表 I2C 事务成功 ≠ 芯片接受了写**(WRTC 写保护层,驱动无回读校验);上电默认写禁止;18H 充电寄存器**非易失**(出厂 82H、上电即充电使能);写允许序列与手册 L392"三者全 1"矛盾待台架确认 | E-32(P2)/E-38/E-39/E-51/E-52/E-60 | +| **GD5F + FTL** | int32 码:0 / -1 ERROR / -2 BUSY_TIMEOUT / -3 ECC / -4 PROGRAM_FAIL / -5 ERASE_FAIL / -6 ID_MISMATCH;**仅 -4/-5 是单元缺陷(可判坏块),-1/-2 是瞬态**;`dhara_error_t` 分类完整(TOO_BAD/ECC/CORRUPT_MAP…)但 **glue 层映射坍缩**;`f_close` = FTL 提交点 | E-31/E-40/E-41/E-42/E-43/E-53~E-57 | +| **TPAFE5160** | 阻塞 API:0 / -1 参数 / -2 BUSY(10ms,唯一硬超时);IRQ 模式 = ISR 双缓冲 + `s_ready`;**FRSTDATA 校验承诺未实现** | E-44/E-45/E-58 | +| **net 消费方** | lftpd(队列+直调混合,控制通道阻塞 recv);timeSync(全队列,从不读 errno → 免疫 E-03);ui_sink(队列 + DONTWAIT + NoClear + EOF 不关监听 = 全项目唯一完全合规消费方,作修复参照);test(直调+队列混合) | E-02/E-04/E-06/E-20~E-24 | +| **模拟器** | 单线程 Winsock 直调,无队列/线程/阻塞模式/代次/回调触发;成功即清 errno(与 MCU 相反) | 16 项保真度差异(附录 D);09-21 类缺陷在 sim **结构性不可复现** | + +--- + +## 3. 发现明细 + +> 每条:位置(复核落点)→ 模式 → 触发 → 影响 → 修复方向。P2 条目压缩为一行式。 + +### 3.1 P0(生产可触发,静默数据丢失) + +#### E-01 `cfg_file_poll` 超长文件检查是死代码 → 用户配置文件销毁链 +- **位置**:`App/cfg_file.c:1151`(`if (len > CFG_FILE_MAX_LEN)`);机制根 `:504`(`s_load_file` 读长钉死 `CFG_FILE_MAX_LEN`,`cfg_file.h:26` =4096);对照组:load 守卫 `:984-993`、save 守卫 `:1032-1041` 均在位,**唯 poll 缺** +- **模式**:P3(阈值检查写在恒假的位置) +- **触发**:`/config.ini` 超过 4096B——文档化的 FTP 编辑工作流 + 外科手术式保存补写(`:774-797`,输出上限 `CFG_OUT_MAX`=5120 > 4096,无回检)都可达 +- **影响链**(已逐环复核):poll 读被截断的前 4096B → 界外段/键当"缺失"静默回工厂默认(`:1164-1183` 原地段复位 + 重解析)→ 回调触发网络按工厂 IP 重建 → 基线刷新(`:1189-1197`)把错误状态"粘住"→ 任何后续保存只按 RAM 副本(截断视图)重写 → **界外内容永久丢失** → 下次开机 `cfg_file_load` 的 `st.size > 4096` 守卫判"损坏" → **整份用户文件销毁**重生工厂模板。全程零错误日志 +- **修复方向**:poll 在 `s_load_file` **之前**查 `cfg_store_stat` 的 `st.size > CFG_FILE_MAX_LEN`,走与 load 相同的"工厂默认 + 重建模板 + **自检留痕**"分支;save 路径加 `out_len ≤ CFG_FILE_MAX_LEN` 守卫(超限拒绝保存并报错,而非写出下一颗雷) + +#### E-31 FTL:`dhara_map_resume` 任何失败 → 整卷映射静默丢弃 +- **位置**:`Drivers/BSP/GD5F2GQ5UE/nand_ftl.c:250-252`(`if (dhara_map_resume(&s_map, &err) < 0) { DBG_INFO("No valid map found..."); dhara_map_clear(&s_map); }`);错误源 `Lib/dhara/journal.c:222-248`(find_checkblock 逐块扫描,单次读失败透传 `*err` 并继续,任一子步骤失败 → resume 返回 -1);分类能力 `Lib/dhara/error.h:20-31` +- **模式**:P3(错误类别坍缩)+ P8(软错误升级为终态数据丢失事件) +- **触发**:resume 扫描(大量页读)中**任何一次**瞬态读失败——ECC glitch / SPI 瞬断 / WIP 轮询超时(2026-09-20 台架瞬态读异常,其低 VDD 归因与"混合页"解读后续均被证伪——见 GD5F Trap 05;曾为它引入的 2ms settle 已于 2026-09-21 移除,就绪判定回归 WIP 轮询) +- **影响**:全部逻辑→物理映射静默丢弃 → FatFs 探针判卷无效 → **整卷重格式化**(2026-09-20 `.fschk` 重格式化循环为同族事故)→ 全部用户数据丢失;仅一行 info 级日志。dhara 本可区分 `DHARA_E_TOO_BAD`(真空白芯片,正当 fresh)与 `DHARA_E_ECC`/`BAD_BLOCK`/`CORRUPT_MAP`(真实完整性故障)——glue 层丢弃了全部区分信息 +- **修复方向**:按 `err` 分类——仅 `DHARA_E_TOO_BAD`/无 checkpoint 判 fresh;其余错误**返回失败给 mount 层**,由自检上报、禁止静默 `dhara_map_clear`(重格式化须显式确认/自愈策略明确) + +### 3.2 P1(22 项) + +#### E-02 netTask 队列头阻塞:一个消费者的阻塞调用冻结全体 +- **位置**:`Drivers/BSP/NET/net_socket.c:357`(`net_process_messages` 一次取一条,处理完才取下一条)、`:1019-1067`(`net_recv_locked` 阻塞等待循环,30s 超时在 netTask 内空转)、`:961-984`(send 每 chunk 1s 停滞等待);触发源 `App/time_sync.c:84`(flags=0 阻塞 recv)、`lftpd_inet.c:106-109`(read_line 阻塞 recv——**FTP 客户端控制连接空闲 >30s 即触发,常见工况**)、`:135/:156`(阻塞 send) +- **模式**:P4 变体(串行化头阻塞,非丢消息) +- **影响**:最长 30s 全网队列停摆——uiTask 100Hz 轮询、FTP 收发/accept、timeSync 全部排在队列里得不到处理;事件处理不受影响(等待循环内仍调 `net_poll`,`:1020`),受影响的**恰是队列**;与 E-06 叠加 → PASV 假 425;STOR 上传方向芯片 2KB 缓冲满 + 重传 8×500ms=4s 耗尽 → 必断 +- **修复方向**:消费端阻塞 recv 改 `DONTWAIT` 分片 + 调用方重投 + 总超时(ui_sink 模式推广到 lftpd/timeSync);或 `net_process_messages` 在长阻塞间隙按 tick 继续排空队列;`NET_RECV_TIMEOUT_MS` 按服务可容忍量级分离(既有 P2-8 是本条的子集) + +#### E-03 全局 `s_net_errno` 跨任务覆写竞态(09-21 修复的残留) +- **位置**:写方 = `net_socket.c:39` 单全局;netTask 侧 `:362/365/375/385/410` + **notify 值构造 `:419-423`**(`s_notified` 分支读同一全局);6 个队列包装器在**调用方上下文** `:660/758/818/1233/1286/1333`(仅失败路径写);6 个直调 API 在**调用方上下文**失败时写(`net_socket :442/449`、`net_bind :482-538`、`net_sendto/net_recvfrom`、`net_getsockname :1953-1964`),**成功不写** → stale 滞留(`:1609` 文档化)。读端:`ui_net_sink.c:460`(uiTask 100Hz,BelowNormal 被抢占窗口最大)、`test/net_test_task.c:230/381` +- **模式**:P2 +- **触发**:包装器写回 errno → 调用方读之间的任何任务切换窗口内,netTask 处理下一条消息(任何其它消费者失败)或他任务直调 API 失败 → 覆写 +- **影响**:uiTask 空闲轮询的 `WOULDBLOCK`(正常态)被误读为真错误 → `net_close(g_client)` **拆掉健康远端连接**(单连接 = 监听同毁 → 端口拒连);测试判读漂移(flaky)。09-21 主失败模式(空轮询误判)已除,跨任务覆写仍在 +- **修复方向**:错误码不再经共享全局——包装器把通知值里的精确码经**出参/返回值**直接交回调用方(通道已有,只差"还原到调用方栈");netTask 侧 locked 函数改以局部变量/出参携带结果码(消除 `:419-423` 的全局读) + +#### E-04 `remo_send` 丢弃 `net_send` 返回值 → 弱网静默截断 +- **位置**:`Drivers/BSP/UI/ui_net_sink.c:117`(`net_send(g_client, p, 4+len+1, 0);` 返回值未用) +- **模式**:P3(假定 `net_send` 恰好返回 len) +- **触发**:LCDMEM 响应 3205B > `NET_SEND_CHUNK_MAX`(1KB),须 4 个 chunk;客户端拥塞时某 chunk 停滞 1s(`NET_SEND_TIMEOUT_MS`)→ 短写/0(net 层已打 "short send" 日志,`net_socket.c:987-992`)→ 响应帧截断 +- **影响**:客户端 CRC 不过判"全程无字节" → 断连;**无应用层日志、无客户端清理**。= 09-21 生产事故的机制在调用方一侧仍未闭环 +- **修复方向**:校验 `ret == len`;短发送 → `DBG_ERROR` + `net_close(g_client)` 强制客户端重连补帧(或入队重试),至少让截断可观测 + +#### E-05 socket 表跨任务写竞态(表属主规则被直调 API 打破) +- **位置**:直调点 `lftpd_inet.c:13,25,50`、`lftpd.c:398,640`、`time_sync.c:40,53`、`ui_net_sink.c:350,360`(`net_socket`/`net_bind`/`net_getsockname`,3 个并发任务);被侵入结构 `net_socket.c:38`(`s_net_socks[]`)、`:1732-1742`(`alloc_socket` 线性扫描置 `in_use`)、`:42/:503-509`(`s_dynamic_port` 全局计数器)、`:436-462/:476-548`(写控制块);对照注释 `:346`("独占 CH395F 和 s_net_socks 访问权") +- **模式**:P7(表属主变体)。**已逐函数核验:TCP 直调路径不触 SPI**(`net_bind` 仅 UDP 分支开 socket 走 SPI,`:511-542`)→ SPI 铁律未破 +- **触发**:`alloc_socket` 扫描/置位 与 netTask 的 `net_close_locked`(`:1167` memset 整块)或事件写 state 在同一 tick 边界交错(ftp/timeSync 同为 Normal 优先级,可时间片抢占) +- **影响**:双任务分到同一 fd,或刚释放的 fd 被复用而芯片侧尚未 CLOSED → 两个服务配置同一 CH395F socket → 偶发"端口行为错乱/连接串扰",难复现 +- **修复方向**:`net_socket`/`net_bind`/`net_getsockname` 并入队列(新增消息类型,与 `net_listen` 同模式);或至少 alloc+init 段临界区 + `s_dynamic_port` 收归 netTask + +#### E-06 `lftpd_inet_accept_timeout` 计时失真 + 跨任务状态预检 +- **位置**:`Drivers/BSP/NET/lftpd/lftpd_inet.c:77-98`(`:93` `elapsed += 100`;`:87-91` `net_get_sock` + `state != LISTENING` 预检) +- **模式**:P3 + P2 +- **触发**:(a) `elapsed+=100` 假定每轮 ~100ms,而 `net_accept` 是队列往返——被 E-02 冻结时单次可达 30s → 名义 5s 超时实际**十几分钟**;(b) ftpTask 跨任务读 netTask 属主表:数据客户端"连接即断"时 state=CLOSED(auto_relisten 尚未执行)的窗口内被读到 → 误判 "data socket closed" → 提前 -1 → **425 假失败** +- **修复方向**:超时改墙钟(`HAL_GetTick` 起点)计量;去掉/降级 state 直读(以 accept 返回值 + WOULDBLOCK 为准;保留则经队列或临界区快照) + +#### E-07 `f_close`(FTL 提交点)返回值 3 处丢弃 → 假成功 +- **位置**:`App/cfg_file_store_mcu.c:83`(原子写:`/config.TMP` + `f_write` 后 `(void)f_close`,只查 fr/bw 就 rename 转正——本移植 `f_write` 可 FR_OK 而数据仅在 cache,**close 才经 `ftl_flush_cache` → dhara 提交**);`App/log/sys_log_mcu.c:110,117`(rc 判定只覆盖 `f_write`+bw → 假成功还**误清 `s_fault_reported` 去重标志** `:127`,后续真失败被静默);`Src/freertos.c:278`(卷探针 `(void)f_close(&__chk)` → "假健康"窗口) +- **模式**:P4 + P8 +- **触发**:瞬态 NAND 写失败(2026-09-20 台架实证,供电实测 3.3V 标称;原"欠压工况"表述已更正) +- **影响**:配置/日志报"成功"而数据未提交 → **掉电即静默丢失**;日志去重被假成功解除 → 真故障不报 +- **修复方向**:close 返回非 FR_OK 即判写失败(配置:unlink 临时文件、不 rename;日志:不清去重标志;探针:归一 FR_DENIED) + +#### E-08 preboot 日志数组并发写 +- **位置**:`App/log/sys_log_mcu.c:39-40`(`s_preboot[8]` + volatile cnt)、`:218-223`(队列**惰性创建**于 `syslog_drain`,defaultTask 上下文,`Src/freertos.c:378` 首调)、`:265-273`(preboot 分支无同步读-改-写) +- **模式**:P7 +- **触发**:内核启动后 ~ 首次 drain 前(defaultTask 存储 bring-up 慢时可达数十秒)的 post 落 preboot 数组——netTask 10s 屏障超时后的 "comms fault"(`net_task.c:126`)与 defaultTask 启动段失败 post(`freertos.c:316/322/343`)**同处"无队列时代"并发** +- **影响**:自检记录丢失/重复(有界,NULL 守卫挡崩溃)——恰是最需要日志的故障窗口 +- **修复方向**:preboot 分支加临界区;或队列创建提前(首个任务上下文的 post 点前) + +#### E-09 UI 读-改-写 × poll 原地更新竞态(P1-3 残余) +- **位置**:`Drivers/BSP/UI/ui_page_net_cfg.c:181`(`net_cfg_t cfg = *net_cfg_get();`)→ 改一字节 → `:186`(`net_cfg_set(&cfg)`,`net_cfg.c:296-301` 整 19B 无锁 memcpy 到 `&s_cfg`)× defaultTask poll 的段原地更新(`cfg_file.c:1171-1182`,`s_parse` 直接写 `p_data=&s_cfg`) +- **模式**:P7(跨任务无同步读-改-写) +- **触发**:FTP 外部编辑(poll 原地刷新 RAM 副本)与 UI 页面读-写窗口跨唤醒 tick +- **影响**:陈旧写回**抹掉外部编辑** → 下次保存旧值落盘 → 外部变更永久丢失 +- **修复方向**:UI 页面用局部副本 + 显式保存提交(保存时才合并写回);或写回前基线比对(检测到副本过期则放弃/提示) + +#### E-10 NAND init 单次无重试 + 内部错误吞 +- **位置**:`App/app_main.c:64-75`(`gd5f2gq5ue_init` 单次调用);`gd5f2gq5ue.c:615-668`(init 内 read_id 恒返 OK、唯一真失败=ID 校验)、`:519-520`(bbt_scan 读失败吞)、`:651/655/659`(`wait_busy(100)` 返回值吞 ×3)、`:653-655`(ECC 使能未校验 → E-54) +- **模式**:P4 + P9 +- **影响**:冷启动瞬态 SPI 毛刺 = 好芯片判 "NAND fault" 整次开机无存储(无重试);反向慢芯片 = 假 OK + 垃圾 BBT(E-42) +- **修复方向**:init(或 reset + ID 读)重试 1~2 次;`wait_busy`/`bbt_scan`/ECC 使能 错误向上传递(与 E-42/E-53/E-54 同批改造) + +#### E-33 CH395F DMA 路径 `void` 化:失败不可见,收路径冒充新数据 +- **位置**:`Drivers/BSP/CH395F/ch395f.c:89-135`(DMA 传输:HAL 错误 → log+return;1s 超时 → log + `HAL_SPI_Abort` + return;DMA 错误完成 → **仅 DBG_INFO 当正常** `:130-134`);`:1117-1147`(`write_send_buf` void → `net_socket.c:972-975` 仍计该块已发);`:1161-1191`(`read_recv_buf` void;`:1190` memcpy **无条件**执行 → 失败时调用方缓冲得到 `s_spi2_dma_rx_buf` 的**陈旧内容**) +- **模式**:P4 + P8(与 09-21 生产 bug 同族:状态对调用方不可见) +- **影响**:发送侧整块数据静默丢失;接收侧陈旧字节当"新数据" → 静默数据损坏(TCP 可掩盖、UDP 丢包);日志有 ERROR 行但**无程序化错误码**,net 层对调用方返回"成功" +- **修复方向**:DMA 传输返回状态(或可查询错误标志 + 序号);失败时 `net_send`/`net_recv` 返回 -1;错误完成分支 INFO→ERROR + +#### E-34 CH395F 字节级路径:`HAL_MAX_DELAY` 无限超时 + 返回值未检查 +- **位置**:`ch395f.c:181`(write_cmd)、`:195`(write_data)、`:210`(read_data)——均 `HAL_SPI_TransmitReceive(..., HAL_MAX_DELAY)` 且返回值丢弃 +- **模式**:P9(无限等待)+ P4 +- **触发**:DMA 错误路径 `HAL_SPI_Abort`(`:124`)后 SPI2 状态机残留卡死 → 下一次字节级命令**永久阻塞** +- **影响**:一次 SPI 故障 = netTask 挂死 = 全网瘫痪(系统无看门狗);开机 init/check_exist 路径同样暴露 +- **修复方向**:改有限超时(100~500ms,与 DMA 路径同量级)并检查返回值,失败向上返回错误 + +#### E-35 测试 Phase5 块长 4093B > DMA 缓冲 2052B → 静默丢弃 + 误导签名 +- **位置**:`test/ch395f_test_task.c:1089`(`PHASE5_CHUNK_SIZE 4093`,注释仍写 "4096B/4KB" `:1214/:1285`)、`:1214`(TX 单次写 4093B)、`:1284-1287`(RX 单次读);驱动守卫 `ch395f.c:1126-1129/:1170-1173`(`len+4 > CH395F_DMA_BUF_SIZE(2052)` → **静默 return 无日志**) +- **模式**:P3(契约漂移)× P4(静默丢弃) +- **触发**:MSS 2048→1024(DMA 缓冲 4100→2052B)后测试常量未同步;台架跑硬件层套件即触发 +- **影响**:每个 TX 块被守卫丢弃 → 芯片发送缓冲恒空 → SENE 恒满足 → TX 毫秒"完成"(吞吐行失真)→ PC 端无数据 → RX 5s 后报 "rx stall" → **失败签名指向 RX,真因(TX 静默丢弃)不可见** → 质量门禁失效并误导排障 +- **修复方向**:测试常量对齐 ≤2048;驱动超尺寸守卫加 DBG 告警并把上限写入头文件契约(现仅文档化 NULL/len>0) + +#### E-36 RS485:ISR 重武装清零 `rx_size` → getter 恒 0 +- **位置**:`Drivers/BSP/RS485/rs485.c:108`(`receive_start` 内 `rx_size = 0`)、`:151-161`(RxEventCallback:set_size → on_rx 钩子 → `receive_start` 清零)、契约 `rs485.h:100-105` +- **模式**:P3(文档化契约结构性不可达——"无数据"误判族) +- **影响**:getter 承诺"任务可获取最近一次接收字节数",实际 ISR 内在钩子后几 µs 就清零 → **任何任务时刻调用恒为 0** → 首个按文档 API 轮询"新帧"的消费者将永远"无数据" → 485 帧结构性不可见。当前 `rs485_task` 为空桩而休眠(E-59) +- **修复方向**:重武装不清零(双缓冲/帧序号);或契约改为"仅 on_rx 钩子内有效"并在头文件明示 + +#### E-37 RS485:`rs485_init` 吞掉 RX 武装失败 +- **位置**:`rs485.c:52`(`(void)rs485_receive_start(p_handle)`);`rs485_init` 返回 `void` +- **模式**:P4(init 路径也漏) +- **影响**:开机时 UART4 状态非 READY(MspInit 异常/重复 init)→ `HAL_UARTEx_ReceiveToIdle_IT` 返回 BUSY/ERROR → RX 从未武装 → **485 接收终身静默死亡、零日志** +- **修复方向**:检查返回值、失败记 FAIL;init 契约改返回码 + +#### E-38 SD2506:负温度解码 ≤-17°C 全错(=既有 P2-5,升级 P1) +- **位置**:`Drivers/BSP/SD2506/sd2506.c:300`(`*p_temp = (int8_t)(val | 0xF0U)` 负分支) +- **模式**:P6(符号转换) +- **机制**:手册 L322 证明芯片用二补码("TEMP=FEH → -2℃");`|0xF0` 技巧仅在 val 高半字节已为 0xF(-16~-1)时成立。-30°C(0xE2) → 0xF2 → 报 -14°C;-20°C → 报 -4°C +- **影响**:设备工作温度 -30~+80°C → 低温环境读数恒错且**返回 0=OK 无指示**(现消费者=开机日志) +- **修复方向**:统一 `*p_temp = (int8_t)val` + +#### E-39 SD2506:ARST 在 CTR3(11H),驱动从未写过 CTR3 → `clear_alarm` 永久不可达 +- **位置**:手册 4.1 寄存器表:**ARST = CTR3(11H) D7**(D6=F32K/D5=TDS1/D4=TDS0/D3~D0=FS3~FS0,复位值 0000-0000);`sd2506.h:55` 定义了 `SD2506_REG_CTR3=0x11` 但**全驱动零次写 CTR3**(grep 证实);`clear_alarm` 契约 `sd2506.h:261-267`、实现 `sd2506.c:470-478`(依赖"ARST=1 时读 CTR1 自动清 INTAF",手册 L398) +- **模式**:P3(契约不可达)/ P8 +- **影响**:ARST 恒为复位值 0 → 报警匹配后 INTAF 锁存且读不自动清除 → 首次报警后 INTAF **永久锁存**;`clear_alarm` 成为"返回 0 但什么都没清"的空操作;INT 脚单事件报警模式下锁存低电平不释放 +- **修复方向**:在写允许窗口置 ARST=1(`write_enable` → 写 CTR3=0x80 → `write_disable`);注意 11H 属 00H~71H 保护段,直接写无效(与 E-60 同一写保护机制);若项目确认不使用 RTC 报警,改在契约中明示"报警功能未启用"并评估移除 + +#### E-40 FTL:erase/prog 任何失败(含瞬态)→ 判坏块并持久化 +- **位置**:`nand_ftl.c:104-113`(`dhara_nand_erase`:`ret != GD5F_OK` → `mark_bad` + `DHARA_E_BAD_BLOCK`——前件 `ret==GD5F_ERASE_FAIL` 被后件完全包含,即**一切失败**都判坏块)、`:121-132`(`program_load/exec` 失败 → mark_bad,同坍缩)、`:141-164`(回读/ECC/数据不符 → mark_bad) +- **模式**:P3(瞬态/终态混淆) +- **影响**:健康块因 `GD5F_BUSY_TIMEOUT`/`GD5F_ERROR` 被**永久除名**并持久化进 BBT 池 → 容量永久损失(2026-09-21 "一轮破坏性 GD5F 测试容量 95248→95200" 的机制性同根);每次误标记触发 BBT 池整块擦写(任务上下文数秒 SPI 阻塞) +- **修复方向**:仅 `GD5F_ERASE_FAIL`/`GD5F_PROGRAM_FAIL`(E_FAIL/P_FAIL 状态位 = 真实单元缺陷)映射坏块;timeout/ERROR 返回 `DHARA_E_ERASE`/`DHARA_E_IO` 由 dhara 重试或上报 + +#### E-41 `gd5f_bbt_save` 编程失败仍返回 OK 并推进版本(=既有 P2-3) +- **位置**:`gd5f2gq5ue.c:1009-1016`(`write_enable/program_load/program_exec/wait_busy` 返回值全丢弃 → `s_bbt_version=ver`、槽前进、`return GD5F_OK`);上游 `gd5f2gq5ue_mark_block_bad` 为 `void`(`gd5f2gq5ue.h:189`) +- **模式**:P4 + P8(void 契约隐藏失败) +- **影响**:BBT 池块编程失败 → RAM BBT 说"此块坏"、闪存副本陈旧 → **重启后 dhara 重新分配该坏块** → 写入静默丢失;调用方结构性无法得知持久化失败 +- **修复方向**:检查编程结果返回错误码;`mark_block_bad` 改返回码;失败重试下一槽(槽轮换框架已存在 `:984-989`) + +#### E-42 `gd5f_bbt_scan` 弃两读返回值 → 基于陈旧缓冲判块 +- **位置**:`gd5f2gq5ue.c:516-526`(`:519` `gd5f_page_read`、`:520` `gd5f_read_from_cache` 返回值均弃;`:522` 判 `spare[0]`——首次迭代未初始化、之后为上一块残留) +- **模式**:P4 +- **影响**:2048 块出厂扫描中任一读失败 → 该块判定基于垃圾(好块判坏 = 容量损失;坏块判好 = 未来数据丢失);缓解:与持久化副本 OR 合并(`:960-969`)兜住已持久化坏块,风险集中首启 +- **修复方向**:检查两读返回值,失败按保守方向(标坏)或重读 + +#### E-43 `gd5f2gq5ue_erase` 缺容量边界检查(与 Trap 03 文档矛盾) +- **位置**:`gd5f2gq5ue.c:783-806`(仅块对齐检查;read/write 均有 `offset+size ≤ GD5F_TOTAL_SIZE`,`:705-707/:748-750`) +- **模式**:P3(契约/文档分歧) +- **触发**:`offset = GD5F_TOTAL_SIZE` → `block_addr=2048` → 24 位行地址(block×64)被芯片解码回范围内块(2048≡0 mod 2048)→ **可能静默擦掉块 0**(journal/BTT 所在) +- **影响**:越界擦除命中活块 = 数据丢失;或芯片忽略 = 静默 no-op。`GD5F_Trap_Records.md` Trap 03 称"三个函数入口统一加边界检查"——代码与文档不符,文档高估了修复范围 +- **修复方向**:erase 补同一容量检查(对齐检查之后);更正 Trap 文档 + +#### E-44 TPAFE:`read_all` 无数据有效性校验(承诺的 FRSTDATA 校验未实现) +- **位置**:`Drivers/BSP/TPAFE5160/tpafe5160.c:151-168`(`read_all`:start_conv → wait_busy(10ms) → read_channels → 无条件 `return OK`);`tpafe5160.h:132-133`(承诺"通过 FRSTDATA 验证第一通道");`TPAFE5160_FRSTDATA_READ` 宏(`h:37`)有定义、**代码零引用**(全库 grep 证实) +- **模式**:P8 + P9 +- **触发**:芯片掉电/虚焊/总线冲突 → BUSY 引脚读低/浮空 → wait_busy 立即"通过" → RD 脉冲读回三态总线钳位态(0xFFFF/0x0000 常量) +- **影响**:上层把常量垃圾当有效采样 → 设备"测量正常"数值全错("无数据误判"的镜像) +- **修复方向**:实现 FRSTDATA 首通道校验(首次 RD 后 FRSTDATA 应为高,否则报错),或至少拒绝全 0/全 1 + +#### E-45 TPAFE:`s_ready` 三写者 → 重触发即丢未消费帧 +- **位置**:`tpafe5160.c:288-291`(`start_conv_irq` 在 CONVST 脉冲前 `s_ready=0`)、`:311-313`(`clear_ready`,任务上下文)、`:354-355`(ISR 置 1) +- **模式**:P5(事件标志语义:电平非计数) +- **影响**:帧 N 完成置 ready → 消费者未及读取 → 下一圈触发清零 → 帧 N **永久丢失**;高速采样系统性丢样,上层无法区分"无新数据"与"数据丢了"(现 adc_task 空桩休眠,E-59) +- **修复方向**:从 `start_conv_irq` 移除 `s_ready=0`(生命周期归消费者)或改帧序号制 + +### 3.3 P2(36 项,一行式) + +**配置/日志/事件** + +| 编号 | 位置 | 问题 | 修复方向 | +|---|---|---|---| +| E-11 | `cfg_file.c:1147-1149` | poll 读失败静默 return 无限重试、无日志(load/save 同场景均有日志+自检) | 加日志 + 连续 N 次转自检 | +| E-12 | `ffconf.h:39`(`FF_FS_LOCK=0`)× `sys_log_mcu.c:100-125` | /log 同文件双开:defaultTask 写(容量满 unlink 重建)× uiTask 浏览读 → 孤儿读/乱码/瞬态 FR_INT_ERR(=既有 P2-10,复核一致) | `FF_FS_LOCK` 或队列化读 | +| E-13 | `freertos.c:200` | `osEventFlagsNew` 返回值不检查 → NULL 句柄时屏障全失效 + defaultTask 失 500ms 节拍紧密循环刷 NAND,零日志 | 检查 + FAIL + 自检 | +| E-14 | `net_task.c:78-80/181-184/205-209` | net init/重建失败仅串口日志,无持久自检记录(对照 PHY 有 comms fault、存储有 storage fault)→ 现场网络故障无痕迹(=既有 P1 级可观测性缺口) | `syslog_post(SYSLOG_SELFTEST,…)` | +| E-15 | `ringbuf.c:38-76` | API 陷阱:满/空 len=0 不可区分、短写静默、无溢出计数(现零调用者,休眠) | 修复契约或移除 | +| E-16 | `cfg_file.c:463/475/907-926` | `s_gen_document` 不查 snprintf 返回值 + 注册不限键名长度 → 未来长键名可致模板缓冲越界 | 检查返回值 + 注册限长 | +| E-17 | `net_task.c:201-203` | RECONFIG `Get`+`Clear` 非原子 TOCTOU(=既有 P2-11)——**本次重评**:命令无载荷(执行读最新 RAM 副本、代次在执行时递增),丢失 = 本次重建跳过、下次保存/重启自愈,无数据丢失 → 维持清单、风险低于原评级 | `osEventFlagsWait` 原子消费 | +| E-18 | `freertos.c:422-424` | ftpTask `WaitAll(osWaitForever)` 无超时 → 生产端故障时 FTP 永久静默阻塞 | 超时 + 降级日志 | +| E-19 | `freertos.c:430`;`sys_events.h:28-29` | 死事件位:`FTP_READY` 置位在 `lftpd_start`(无限循环)返回后 = **不可达**;`TIMESYNC_READY` 从未置位 → 未来消费者 Will 永久阻塞 | 删除或改在首次监听成功后置位 | +| E-20 | `lftpd_inet.c:100-127`、`lftpd.c:555-561` | 控制通道 30s 阻塞 recv 兼存活判定 + 行>511B/EOF/真错误同返 -1 → 慢客户端被断、长命令杀会话、叠加 E-02 | DONTWAIT + 调用方状态机;超长回 501 保会话 | +| E-21 | `time_sync.c:128-144/:80-97` | accept 一切失败=无连接(200ms 静默重试、无 errno 分类/日志)+ 30s 阻塞 recv + close-then-rebuild 空窗 | errno 分级(BADF/INVAL→重建+日志);DONTWAIT + 会话超时(=既有 P2-8 扩展) | +| E-22 | `lftpd.c:608-626`、`time_sync.c:96/:146-149` | close-then-relisten 每会话端口空窗 ~0.2-1.7s(合规对照 = `ui_net_sink.c:446-457`:EOF 不 close、靠 auto_relisten 常驻) | 向 ui_sink 模式看齐 | +| E-23 | `test/net_test_task.c:869-917/:296-403` | 监听主循环与 phase7 无代次检查(网络重建后恒 BADF 空转)+ TC704 8 路 socket 与 3 个并发服务抢 8 槽 fd 池 → 假 FAIL | 代次检查 + 降并发/测试构建抑制服务任务 | +| E-24 | `net_socket.c:1971-1975 × :162`;`lftpd.c:398-413` | `s_local_ip` 逐字节拼读 × reconfig 重写不原子 → PASV 227 响应 IP 混拼(低频人工触发) | getsockname 入队列或临界区快照 | +| E-25 | `net_socket.c:1609`;`net_select.c:63-65`;`net_socket.c:751-753` | API 一致性杂项:成功不清 errno(纪律文档化,读端须知);net_select 参数错误不设 errno(仅测试用);net_accept 失败也覆写 `*addrlen`。"LISTENING 恒可读"部分 = 既有 P2-7 | 统一 errno 纪律 + 契约注释 | +| E-26 | `net_socket.c:1351-1519` | UDP 直调 API(sendto/recvfrom/UDP-bind)在调用方上下文做 SPI——**潜在地雷**:生产零调用(timeSync 2026-08-28 起 TCP 化正是为绕开),首个 UDP 用户即破铁律 | 文档化禁令或并入队列 | +| E-27 | `Src/main.c:104-113` | CubeMX `MX_*_Init` 返回值忽略(生成代码)→ 下游驱动级检查兜底(check_exist/gd5f ID/sd2506 init),可接受,记录在案 | 保持(生成区不改) | +| E-28 | `ui_port.c:60/:163-168` | 远端按键单格缓存(同任务无竞态):10ms 内连按前键丢失(UX 微瑕) | 小环形或文档化 | +| E-29 | `ui_net_sink.c:223-227` | LCDMEM 取帧失败静默丢应答、无日志(现网实际不触发) | 加 DBG 日志 | + +**驱动层** + +| 编号 | 位置 | 问题 | 修复方向 | +|---|---|---|---| +| E-32 | `sd2506.c:182,191`;`sd2506.h:126` | **(2026-09-21 复核后由 P0 降档)**init 两笔写在写禁止态发出被芯片忽略(L394/537 属实),**但**手册 §4.8"出厂均配置成 82H"+ 18H 非易失 → 充电器上电即已使能,**原"充电器终身未使能"P0 链证伪**;残留问题 = ① `:187-190` 注释与代码矛盾(注释称写 0x20 且"ARST=1",实际写 0x7B 且 ARST 在 CTR3 不在 CTR1)② init 无回读校验(18H 若被长时掉电腐蚀则无人发现)③ `docs/硬件平台/V0.0_网表详细分析报告.md:280`"已核实无缺陷"无回读证据 | 18H 改"回读验证 0x82,≠ 则 write_enable→写→回读→write_disable";注释更正(0x7B = 手册 L396 推荐的写禁止态 CTR1 值;ARST 在 CTR3) | +| E-46 | `ch395f.h:151` | `0xECH` 无效 ARMCC 字面量(= SD2506 0x0FH 同族);当前零引用休眠,首个使用者即踩 | 改 `0xECU` + pre-commit 禁 `0xNNH` 检查 | +| E-47 | `ch395f.c:606-622` | 轮询超时与芯片忙共用 `0x10` 码 + 跨命令陈旧状态(共享 CMD_STATUS 读即清 → 下条命令可能先读到上条的完成态);现边界统一按可重试处理,影响有限 | 独立超时码或轮次序号 | +| E-48 | `rs485.c:173` | ErrorCallback 重武装返回值丢弃(现良性:ORE 路径 HAL 先复位 RxState=READY;非阻塞错误路径原武装存活自愈) | 非 HAL_OK 记 WARN | +| E-49 | `rs485.c:70-93` | TX 超时立即拉低 DE → 截断在途尾字节(仅超时路径,本身已失败) | 等 TC/短延时后再拉低 | +| E-50 | `rs485.c:37-39` | rx_buf/rx_size 单缓冲无 ISR/任务同步 → 任务拷贝中遇新帧 = 帧撕裂(115200×256B≈22ms 窗口) | 双缓冲+序号或契约限定钩子内 | +| E-51 | `sd2506.c:450-451` | set_alarm CTR2 全量覆盖(=0x25)中途清掉 WRTC1 → 芯片中途重新上锁 → 后续 write_disable 全被忽略,停在混合态(下轮 write_enable 自愈) | 读-改-写保 WRTC 位 | +| E-52 | `app_main.c:89/96/104/109` | 开机诊断块 4 个 `get_*` 返回值全丢弃 → I2C 瞬断时打印**未初始化栈值**当诊断(误导性证据) | 检查返回值,失败打印"读取失败" | +| E-53 | `gd5f2gq5ue.c:456/485/684-685` | 轮询路径 HAL 返回值系统性未检查(DMA 路径有检查)→ 状态字节读垃圾 → 假"芯片空闲" → 后续命令未锁存却报成功 | 统一检查,失败返 GD5F_ERROR | +| E-54 | `gd5f2gq5ue.c:653-655` | init 的 ECC 使能步骤未校验 → "NAND init OK" ≠ ECC 已开 → 存储栈在无 ECC 保护下运行(数据完整性基座静默降级) | 使能后回读 B0h 校验 bit4 | +| E-55 | `nand_ftl.c:206/:180` | `dhara_nand_read/is_free` 弃 `read_from_cache` 返回值 → 传输失败时以陈旧/垃圾冒充成功(is_free 方向:垃圾 0xFF → 已用页判空闲 → 数据覆盖)(=既有 P2-4) | 检查返回值失败上报 | +| E-56 | `gd5f2gq5ue.c:703-737` | 公共 `gd5f2gq5ue_read` 对不可纠错页仍返回 0(契约"0=成功"实为"SPI 传输完成";生产主路径有 dhara glue ECC 检查保护,缺口仅在 raw API) | 加 ECCS 检查或头文件明示 | +| E-57 | `gd5f2gq5ue.h:119/140/189/222-253` | 公共 API 返回类型 `int`/`int32_t` 混用(码值均为小负数,无功能影响) | 统一 `int32_t` | +| E-58 | `tpafe5160.c:322-324/:343/:354` | 双缓冲角色互换 + 任务抢占 → 撕裂读(get_buf 与拷贝完成之间被抢占超一个帧周期 = 8 通道失去同步性) | 原子快照拷贝 API(关中断+memcpy)或文档化时限 | +| E-59 | `adc_task.c:37-43`、`rs485_task.c:37-43` | 边界状态:两任务均为空桩(osDelay 循环),`rs485_on_rx` 无覆盖 → E-36/E-44/E-45/E-58 全部休眠;**真实消费者接入之日即触发** | 消费者实现前先修驱动契约(E-36/E-45/E-44) | +| E-60 | `sd2506.c:121-133`(write_enable);手册 L392 vs 实测 | **(2026-09-21 复核新增,待台架确认)**write_enable 实际置 WRTC1=1(CTR2=0x80)、WRTC3=1(CTR1=0x84 D7)、**WRTC2=0**(CTR1=0x84 D2);手册 L392 明写"WRTC1=1、WRTC2=1、WRTC3=1 时写允许"(三者全 1)→ 若手册准确则时间写(00H~06H)应全部无效,但与台架实测(RTC 套件 set==readback PASS,写入确已生效)**矛盾**——二者必有一处不符(手册"三者全 1"可能比实际硬件宽松,或 WRTC2 门控范围与描述不符) | 台架实测:write_enable 后回读 CTR1/CTR2 记录 WRTC 位实际态 + 验证时间写生效;据此订正手册理解或补 write_enable 的 WRTC2 位(暂按"硬件实测为准",不阻断当前功能) | + +--- + +## 4. 模式 × 子系统矩阵 + +> F=发现编号(E-);✓=检查无问题;—=不适用 + +| 模式 | net 层/执行器 | 事件标志 | 配置引擎 | syslog | CH395F | RS485 | SD2506 | GD5F/FTL | TPAFE | lftpd | timeSync | ui_sink | test | sim | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| P1 值拷贝 | ✓(契约修复在位) | — | ✓(输入型条目) | ✓(输入型条目) | — | — | — | — | — | — | — | — | — | — | +| P2 共享全局 | **E-03** | ✓ | ✓ | ✓ | — | — | — | — | — | — | ✓(免疫) | **E-03** | E-03 | E-30 | +| P3 混同 | E-25/E-26 | — | **E-01** | — | E-35/E-47 | E-36 | E-39/E-51 | **E-31/E-40**/E-43/E-56 | — | E-06/E-20 | E-21 | **E-04** | ✓ | E-30 | +| P4 未检查 | ✓(put 全查) | E-13 | E-11 | E-08 | **E-33/E-34**/E-35 | E-37/E-48 | E-32/E-52 | E-41/E-42/E-53/E-54/E-55 | — | ✓ | E-21 | ✓ | E-23 | — | +| P5 事件语义 | ✓ | E-17/E-18/E-19 | ✓ | — | — | — | — | — | **E-45** | ✓ | ✓ | ✓ | ✓ | — | +| P6 符号/宽度 | — | — | — | — | — | — | E-38 | — | — | — | — | — | — | — | +| P7 跨上下文 | **E-05** | — | **E-09** | E-08 | — | E-36/E-50 | — | — | E-58 | E-06 | — | — | E-23 | — | +| P8 外设/库 | E-26 | — | **E-07** | E-07 | E-33/E-46 | — | E-32/E-60 | E-43/E-54/E-56 | E-44 | — | — | — | — | — | +| P9 超时/错误 | **E-02** | — | — | — | E-34/E-47 | E-49 | — | **E-31/E-40** | E-44 | E-20/E-22 | E-21 | — | E-23 | — | + +--- + +## 5. 修复计划(三档;待批准后实施,本次审计不改代码) + +### 第一档:立即(改动小、价值高、风险低,无架构变更) + +| # | 项 | 内容 | 关联 | +|---|---|---|---| +| 1 | **E-32**(降 P2) | SD2506 init:18H 改"先回读验证 0x82,≠ 则 write_enable→写 18H=0x82→回读→write_disable"(18H 非易失、出厂 0x82,正常路径保留原写);注释更正(0x20/ARST 误注 → 0x7B 为手册 L396 推荐写禁止态值;ARST 在 CTR3 非 CTR1) | P2 | +| 2 | **E-31** | `dhara_map_resume` 按 `dhara_error_t` 分类:仅 `DHARA_E_TOO_BAD` 判 fresh,其余返回失败给 mount 层 + 自检上报,禁止静默 clear | P0 | +| 3 | **E-01** | `cfg_file_poll` 前置 `st.size > CFG_FILE_MAX_LEN` 守卫(工厂默认+模板+自检留痕);save 加 `out_len ≤ 4096` 守卫 | P0 | +| 4 | **E-34** | CH395F 字节级路径:`HAL_MAX_DELAY` → 有限超时(100~500ms)+ 返回值检查 | P1 | +| 5 | **E-33** | CH395F DMA 传输加错误通道(返回状态/错误标志);失败 → net_send/recv 返回 -1;错误完成分支 INFO→ERROR | P1 | +| 6 | **E-36/E-37** | RS485 契约修复:重武装不清零(或 getter 契约改"钩子内有效"并明示);`rs485_init` 返回码 + 失败日志 | P1 | +| 7 | **E-43** | `gd5f2gq5ue_erase` 补容量边界检查;更正 Trap 03 文档 | P1 | +| 8 | **E-38** | SD2506 负温度统一 `(int8_t)val`(1 行) | P1 | +| 9 | **E-40** | FTL erase/prog:仅 E_FAIL/P_FAIL 判坏块;瞬态 → `DHARA_E_ERASE/DHARA_E_IO` | P1 | +| 10 | **E-41/E-42** | `bbt_save` 检查编程结果(返回码+重试下一槽,`mark_block_bad` 去 void);`bbt_scan` 检查读返回值 | P1 | +| 11 | **E-44/E-45** | TPAFE:实现 FRSTDATA 首通道校验(或拒全 0/全 1);`start_conv_irq` 移除 `s_ready=0`(或改帧序号) | P1 | +| 12 | **E-07** | `f_close` 返回值检查 ×3(store 原子写 / syslog ×2 / 卷探针):close 失败 = 写失败 | P1 | +| 13 | **E-04** | `remo_send` 校验 `ret == len`;短发送 → DBG_ERROR + `net_close` 强制重连补帧 | P1 | +| 14 | **E-03** | errno 出参化:包装器把通知值里的精确码经出参/返回值直传调用方;netTask 侧 locked 函数改局部/出参结果码(消除 `:419-423` 全局读) | P1 | +| 15 | **E-09** | UI 页面局部副本 + 显式保存提交(或写回前基线比对) | P1 | +| 16 | **E-08** | preboot 分支临界区(或队列创建提前) | P1 | +| 17 | **E-39/E-51** | SD2506:写允许窗口内置 ARST=1(写 CTR3=0x80,注意 11H 属保护段须经 write_enable);set_alarm CTR2 改读-改-写保 WRTC 位(按手册 L396 推荐值 0xFF/0x7B) | P1/P2 | +| 18 | **E-35** | 测试 Phase5 常量对齐 ≤2048;驱动超尺寸守卫加告警 + 头文件契约 | P1 | +| 19 | **E-46** | `0xECH` → `0xECU`;pre-commit 增加 `0xNNH` 字面量检查 | P2(1 行) | +| 20 | **E-52** | 开机诊断块检查 `get_*` 返回值 | P2 | +| 21 | **E-17** | RECONFIG 改 `osEventFlagsWait` 原子消费(= P2-11 闭环) | P2 | +| 22 | **E-14** | net init/重建失败补 `syslog_post(SYSLOG_SELFTEST,…)` | P2 | + +### 第二档:下一迭代(结构性改造) + +| # | 项 | 内容 | +|---|---|---| +| 1 | **E-02+E-06+E-20+E-21** | 阻塞模型改造:lftpd 控制通道/timeSync 改 `DONTWAIT` + 调用方状态机(ui_sink 模式推广);`net_process_messages` 长阻塞间隙按 tick 继续排空队列;`accept_timeout` 墙钟计时 + 去跨任务 state 直读 | +| 2 | **E-05** | `net_socket/net_bind/net_getsockname` 并入队列(新消息类型);过渡方案:alloc+init 临界区 + `s_dynamic_port` 收归 netTask | +| 3 | **E-10/E-42/E-53/E-54** | GD5F 驱动错误通道统一:init 重试 1~2 次;`wait_busy`/轮询路径 HAL 检查;ECC 使能回读校验;错误向上传递 | +| 4 | **E-22** | lftpd/timeSync 向 ui_sink close 规则看齐(EOF 不 close、auto_relisten 常驻、仅代次变化/判死时重建) | +| 5 | **E-23** | 测试套件加代次检查(accept 失败分支);TC704 降并发或测试构建抑制服务任务 | +| 6 | **E-30(部分)** | sim 后端 errno 语义对齐(成功不清)+ 缺陷记录标注 sim 复现盲区 | +| 7 | **E-60** | 台架实测:write_enable 后回读 CTR1/CTR2 记录 WRTC 位实际态并验证时间写生效;若 WRTC2 确门控时间写范围 → 补 write_enable;否则(当前时间写已实证生效)→ 文档化实测结论、订正手册理解、驱动契约加注 | + +### 第三档:长期 + +- 模拟器保真度全面对齐(阻塞模式 = select+超时 / connect CONNECTING 状态机 / accept fd 模型),或文档化"阻塞路径仅 MCU 可验证"(附录 D 逐项) +- E-26 UDP 路径并入队列或移除 + 禁令文档化 +- E-12 `FF_FS_LOCK`;E-15 ringbuf 修复/移除;E-19 死事件位清理;E-18 ftpTask 等待超时;E-13 句柄检查 +- 驱动健壮性长尾:E-47/E-48/E-49/E-50/E-55/E-56/E-57/E-58 +- 消费者任务(adc_task/rs485_task)实现时按 AGENTS.md 注册 + 栈预算,**先修 E-36/E-44/E-45 再接入**(E-59) + +--- + +## 6. 与既有《项目审计改进清单》交叉引用 + +| 既有编号 | 本次对应 | 处置 | +|---|---|---| +| P2-2(ch395f DMA 超时未向上传播) | **E-33** | 独立复核一致(DMA 路径 void 无错误通道) | +| P2-3(bbt_save 丢返回值) | **E-41** | 独立复核一致;并入修复计划第一档 #10 | +| P2-4(dhara_nand_read 弃 read_from_cache) | **E-55** | 独立复核一致 | +| P2-5(负温度解码) | **E-38** | 独立复核一致并**升级 P1**(≤-17°C 全错 + 返回 OK 无指示;既有清单 high 优先级,与 P1 定级一致) | +| P2-6(WRTC 写时序) | **E-51**(子集) | 复核一致:set_alarm CTR2 全量覆盖中途清 WRTC1 → 写保护态混合(并入第一档 #17) | +| P2-7(net_select LISTENING 恒可读) | **E-25**(部分) | 复核一致;本次补充同函数"参数错误不设 errno"子项 | +| P2-8(time_sync 30s 卡死) | **E-02/E-21** | **重大扩展**:30s 不只是同步任务卡死,而是**全体消费者队列停摆**(头阻塞)——修复方案相应从"短超时"升级为阻塞模型改造 | +| P2-10(/log unlink×读竞态) | **E-12** | 独立复核一致 | +| P2-11(RECONFIG 非原子) | **E-17** | 复核一致;**重评为 P2**(命令无载荷、丢失自愈、无数据丢失)——建议从清单 high 调整为 medium | +| P1 级"网络故障无痕迹"(清单 :93) | **E-14** | 独立复核一致 | +| P2-1、P2-9、P2-12~16、P3-1~13 | — | 本次未覆盖其主题(P2-1 外科手术式保存超长行为相邻独立项;其余为装饰宏/构建脚本/栈表等),维持原跟踪 | + +--- + +## 7. 健康结论(审计深度证据:检查过且无问题的关键点) + +1. **syslog 队列条目纯输入型**(id + 静态串指针,消费者不回写、生产者不回读)→ 结构性免疫 09-21 类值拷贝陷阱(对照 `net_msg_t.result` 回传字段 = 事故根因) +2. **就绪屏障 5 处等待全部 `osFlagsNoClear`**(`freertos.c:422-424`、`time_sync.c:113-114`、`ui_task.c:48-49`、`net_task.c:177-178`、`ch395f_test_task.c:1387-1388`)——09-21 修复在位;命令位均为单一属主消费 +3. **CFG_SAVE 协议无损**:可消费唤醒位 + `s_save_pending` 锁存 + `s_save_result` 单字节状态机回传 UI——全时序窗口(含 <500ms 双保存合并 = 无损,保存恒以当前 RAM 副本为准)无丢失、无"错误显示成功"可能 +4. **通知值通道独占**:全库无 `osSignal/osThreadFlags`/流缓冲调用者;`LV_USE_OS=0`(MCU)/`LV_OS_NONE`(sim)→ LVGL osal 不竞争;陈旧通知值无假唤醒(`xTaskNotifyWait` 以 pending 状态为准) +5. **`net_select` 纯 RAM 轮询**(调用方无 SPI);"LISTENING 恒可读"偏乐观(现无实际影响,= P2-7) +6. **defaultTask 存储自愈健全**:双确认探针、短写归一 FR_DENIED、`FR_INVALID_NAME` 排除重格式化集、写保护类不重格式化 +7. **内核前纪律**:`app_main_init` 零 CMSIS 调用(HAL_Delay = tick 轮询,非 osDelay);对象创建次序(队列/任务先于 `osKernelStart`);栈溢出/堆失败钩子 = 裸 UART(临界安全) +8. **time_sync 全队列路径、从不读 errno** → 免疫 E-03;**netMsgQueue 深度 8 + `osWaitForever` put**(无丢消息)+ put 返回值全检查 +9. **lftpd 静态缓冲与单会话模型一致**(ftpTask 栈峰值复测 1184B/4096B,余量 71%);队列 wrapper 同步语义挡住 fd 复用竞态(`net_close` 阻塞至 netTask 完成 memset) +10. **lftpd PASV IP 双重字节序转换等价正确**(192.168.1.100 各节验证);recv 0=EOF 在 lftpd/timeSync/ui/phase6/8/10 全部正确区分 +11. **CH395F 中断读清**在单一 SPI 事务内完成(无事务内竞态);DMA 1s 超时 ≫ 最坏传输 ~7ms(无超时误报);`check_exist` 主动回显校验(死芯片不误判);边界检查 `!= CH395F_ERR_SUCCESS` 一致 +12. **CH395F/GD5F 共享 DMA 标志** `volatile` + 字对齐(M4 原子);GD5F CCM 守卫(`gd5f_dma_buf_ok` + 阈值)防 DMA 不可达缓冲入 DMA 路径 +13. **GD5F BBT 加载安全方向**:magic+CRC 双校验,读失败降级出厂扫描;slot 轮换有显式终止(无死循环);FTL 生产主路径读/回读均有 ECC 检查 +14. **GD5F Trap 01/02/03 修复在位**(页地址擦除、read/write 边界检查)——唯 erase 缺边界(E-43,文档与代码不符) +15. **RS485 HAL 错误路径自愈**(F4 gState/RxState 分离:阻塞发送期间重武装可用;ORE 路径 HAL 先复位 RxState=READY)→ E-48 仅健壮性问题 +16. **SD2506 时间写原子性**(7 字节一次写,满足"不可单独写时间寄存器");24h 位/2099 回绕正确;电池电压寄存器 1AH[7]+1BH 9 位与手册一致("读错寄存器"假设被手册证伪) +17. **边界调用方正确**:app_main 对 GD5F 7 码 + TPAFE OK/TIMEOUT 分别映射;sys_clock 全检查 sd2506 返回值 +18. **TPAFE 10ms BUSY 超时** ≫ t_CONV(µs 级)无误报;`s_ready/s_buf_sel` volatile 在位 +19. **已证伪假设留档**(防重复劳动):SD2506 电池电压读错寄存器(手册证伪);RS485 错误回调杀死并发发送期 RX(HAL 源证伪);ch395f.c "2s×HAL_Delay(1) 忙等"(当前代码不存在);SD2506"上电 18H 写无效 → 充电器终身未使能"(手册 §4.8"出厂均配置成 82H"+18H 非易失证伪,原 E-32 P0 链不成立,降 P2,见 §3.3) +20. **模拟器方向**:`ui_net_sink` 已双模型自适应(单/多连接 accept fd);sim 构建仅编 UI+net_sink+配置日志引擎 → lftpd/timeSync 的 fd 泄漏类缺陷当前未触发(E-30 潜在项) + +--- + +## 8. 审计边界与未覆盖项 + +- **未逐行审计**:`Lib/FatFs` 移植层内部(已知陷阱已在 AGENTS.md 固化:FA_OPEN_APPEND 无 FA_WRITE、短写 FR_OK、8.3 短名)、`Lib/dhara` 内核(仅 audit glue 映射层与错误分类)、lftpd 的字符串/IO 辅助模块(边界抽查)、`Middlewares/` CMSIS/HAL 内部(错误路径抽查) +- **模拟器保真度**:16 项差异全表见附录 D;核心结论 = **09-21 类 errno 缺陷在 sim 结构性不可复现**(单线程 + 成功即清 errno 双重掩盖),该类修复只能硬件闭环验证 +- **驱动契约缺陷的"休眠"性质**:RS485/TPAFE 侧多项 P1(E-36/E-44/E-45/E-58)因消费者任务为空桩而暂不可触发——**接入消费者之前必须先修**(§5 第三档) +- 本审计基于 2026-09-21 代码状态(`v2-on-ui` @ `a9aa8ce`);后续代码变更需增量复核 + +--- + +## 附录 A:NET_ERR 错误码表(`net_types.h:98-109`) + +| 码 | 值 | 含义 | 通知值语义 | +|---|---|---|---| +| NET_ERR_OK | 0 | 成功 | 成功:通知值 = 字节数/fd(≥0) | +| NET_ERR | -1 | 通用错误 | 失败:通知值 = 负的精确错误码 | +| NET_ERR_BADF | -2 | 无效 fd(含代次失效) | | +| NET_ERR_INVAL | -3 | 参数无效 | | +| NET_ERR_NOMEM | -4 | fd 池/缓冲耗尽 | | +| NET_ERR_NOTCONN | -5 | 未连接 | | +| NET_ERR_ISCONN | -6 | 已连接 | | +| NET_ERR_WOULDBLOCK | -7 | 无数据/不可写(正常轮询态,**非错误**) | | +| NET_ERR_CONNRESET | -8 | 对端复位 | | +| NET_ERR_TIMEDOUT | -9 | 超时(recv 30s / send chunk 1s / listen 重开) | | +| NET_ERR_NOSPACE | -10 | 芯片缓冲满 | | +| NET_ERR_BUSY | -11 | 芯片忙(含 listen 端口占用) | | + +超时参数(`net_config.h`):`NET_SEND_TIMEOUT_MS=1000`、`NET_RECV_TIMEOUT_MS=30000`(E-02/E-21 的改造对象)。 + +## 附录 B:事件位表(`App/sys_events.h`) + +| 位 | 名称 | 语义 | 置位者 | 消费者(等待方式) | +|---|---|---|---|---| +| bit0 | NET_READY | 状态位 | netTask(组网成功) | ftp/ui/timeSync(`WaitAny|NoClear` ✓) | +| bit1 | STORAGE_READY | 状态位 | defaultTask(挂载+配置加载完成) | 同上 | +| bit2 | FTP_READY | **死位**(置位代码不可达,E-19) | (lftpd_start 返回后——永不) | 无 | +| bit3 | TIMESYNC_READY | **死位**(从未置位,E-19) | 无 | 无(timeSync 实际等 NET/STORAGE) | +| bit4 | NET_RECONFIG | 命令位(可消费) | defaultTask(UI 保存网口配置) | netTask(Get+Clear 非原子,E-17) | +| bit5 | CFG_SAVE | 命令位(可消费) | uiTask | defaultTask(`WaitAny` 500ms + pending 锁存 ✓) | +| bit28-30 | — | 保留 | — | — | + +## 附录 C:项目队列清单与回传机制 + +| 队列 | 定义处 | 深度 | 条目形态 | 回传机制 | 免疫/风险 | +|---|---|---|---|---|---| +| `netMsgQueue` | `Src/freertos.c:166` | 8 | `net_msg_t` **按值**(fd/长度/输入指针) | 任务通知值(精确错误码/字节数);指针字段回写(net_accept 地址) | 09-21 后契约健全;残留 = 全局 errno 竞态(E-03)、头阻塞(E-02) | +| syslog 队列 | `sys_log_mcu.c:218-223`(惰性) | 64 | 输入型(id + 静态串指针) | 无回传(fire-and-forget) | 结构免疫 P1;preboot 窗口(E-08) | +| `cfgSaveQueue` | `freertos.c`(defaultTask/uiTask 间) | 8 | 输入型(路径指针) | `s_save_pending` 锁存 + `s_save_result` 状态机 | 协议无损(§7-3) | + +## 附录 D:模拟器保真度差异表(MCU `net_socket.c` vs `simulator/sim/net_socket_sim.c`) + +| # | 项目 | MCU 后端 | sim 后端 | 影响 | +|---|---|---|---|---| +| 1 | accept fd 模型 | 返回监听 fd 自身(单连接 `g_client==g_listen`) | 返回独立新 fd,监听 fd 常驻(`sim:331-347`) | lftpd 式生命周期在 sim 泄漏 fd(移植即触发);ui_sink 已双模型自适应 | +| 2 | EOF 后行为 | auto_relisten:fd 回 LISTEN、in_use 保持 | 无:fd 仍 in_use 但 Winsock 已关,二次 recv 报通用 NET_ERR | 依赖"EOF 后 fd 仍活"的写法在 sim 失真 | +| 3 | recv 阻塞模式 | flags=0 阻塞,30s → -1/TIMEDOUT | 恒非阻塞(FIONBIO),`(void)flags`(`sim:447` 已复核),无 TIMEDOUT 路径 | **生产阻塞路径在主开发后端零覆盖**(E-02/E-06/E-20/E-21 类缺陷 sim 不可见) | +| 4 | send 停滞契约 | 分块阻塞;1s/chunk 停滞 → 返回已发字节(可 0)+ TIMEDOUT | 单次非阻塞:WSAEWOULDBLOCK → 0 + WOULDBLOCK;部分发送 → 部分值 + **NET_OK(静默截断无告警)** | 同一条件双端 errno 不同 | +| 5 | 零长度 send | -1/INVAL | 0/OK | phase10 零长用例 sim 必假 FAIL | +| 6 | 零长度 recv | -1/INVAL(`len<=0`) | 0 = 对端关闭(`sim:453` 检查为 `len<0` → recv(0) → EOF 分支) | 边界契约不一致 | +| 7 | net_connect 语义 | 异步:0=issued,netTask net_poll 推进 CONNECTING→ESTABLISHED | 进行中 = -1/WOULDBLOCK;state 永不推进(net_poll 空操作) | TC701 假 FAIL / TC703 负向用例**假 PASS** | +| 8 | 错误码粒度 | 12 种精确码(附录 A) | 仅 BADF/INVAL/WOULDBLOCK + 通用 NET_ERR | 依赖精确码的分类逻辑在 sim 失真 | +| 9 | 成功后 errno | **不清除**(stale 滞留,`net_socket.c:1609`) | **清零 NET_OK**(12 处,`sim:461` 等已复核) | **09-21 类缺陷在 sim 结构性不可复现**(+ 单线程无交错)——主开发后端对该缺陷族零灵敏度 | +| 10 | net_listen 前置 | 须 BOUND/CREATED | 仅查 in_use | sim 更宽松 | +| 11 | getsockname 数据源 | s_local_ip 缓存(静态 IP;DHCP → 0.0.0.0) | Winsock getsockname(INADDR_ANY → 127.0.0.1) | PASV 响应 IP 双端不同 | +| 12 | 代次号 | reconfig 递增 | 恒 0 | 重建/重监听路径 sim 零覆盖 | +| 13 | 事件回调 | net_poll 内 fire_event | 只存储不触发 | 回调驱动逻辑 sim 全死(当前无注册者) | +| 14 | 并发模型 | 队列 + netTask 串行 SPI | 单主线程直调 Winsock | 跨任务 errno 竞态 sim 不可复现 | +| 15 | UDP bind | open_socket + SPI(仅 UDP 分支) | 无对应步骤 | 当前无 UDP 用户 | +| 16 | net_init | 芯片全量初始化 | 仅 WSAStartup | 设计对齐 | + +--- + +*报告完。60 项发现全部带 file:line 落点;P0/P1 核心机制经父审计组逐行复核(含 `docs/芯片手册/SD2506API-G.md` L392-398/L537/§4.8 原文核对与 `SD2506_Trap_Records.md` 硬件观测交叉验证)。2026-09-21 复核修订:E-32 由 P0 降 P2(充电器 P0 链被"出厂 82H"证伪)、E-39 机制改写(ARST 在 CTR3、驱动从未写过 CTR3)、新增 E-60(write_enable WRTC2 与手册矛盾,待台架确认)、§6 交叉引用补 P2-2/P2-6。未修改任何代码、未执行构建。* diff --git a/test/gd5f_test.h b/test/gd5f_test.h index 04b38f7a..68e58826 100644 --- a/test/gd5f_test.h +++ b/test/gd5f_test.h @@ -20,6 +20,7 @@ * Phase 8 | FTL 层(nand_ftl_init / nand_ftl_format) * Phase 9 | FatFS diskio(可选) * Phase 10 | 边界条件(offset 越界 / size=0 / 未擦除写入 / 写保护解除) + * Phase 11 | 13h 锁存 / WIP 提前清零对照复现(编程擦除风暴 + 立即读比对,2026-09-21) */ #ifndef GD5F_TEST_H @@ -48,6 +49,7 @@ extern "C" { //#define ENABLE_GD5F_PHASE8_TESTS //#define ENABLE_GD5F_PHASE9_TESTS //#define ENABLE_GD5F_PHASE10_TESTS +//#define ENABLE_GD5F_PHASE11_TESTS /* 13h 锁存复现(仅擦写最高一块,独立门控,可随时启用) */ /* ======================== 测试统计与断言 ======================== */ diff --git a/test/gd5f_test_task.c b/test/gd5f_test_task.c index 83f9d99a..099ad6b4 100644 --- a/test/gd5f_test_task.c +++ b/test/gd5f_test_task.c @@ -785,6 +785,151 @@ static void gd5f_phase10(void) } #endif /* ENABLE_GD5F_PHASE10_TESTS */ +/* ------------------------------------------------------------------- + * Phase 11 — 13h 锁存 / WIP 提前清零对照复现(2026-09-21 台架实验) + * + * 复现对象 = 09-20 两项读异常(归因/证伪史实见 GD5F Trap_Records + * Trap 05,本用例只承担复现职能): + * H1: 编程/擦除风暴后 13h 命令未被锁存(空操作)→ 0Bh 读残留缓存 + * H2: WIP 早于 array→cache 整页装载完成而清零 → 0Bh 读混合页 + * 设计: + * 每轮:擦块 → 64 页背靠背编程(风暴,每页唯一图案)→ 立即(零间隔, + * 最坏时序)13h 读 3 个非相邻页并逐字节比对。 + * 读目标刻意避开最后编程的页(页 63):若 13h 为空操作,残留缓存即 + * 最后一次编程的数据,读页 63 会巧合一致而掩盖故障;读其他页时, + * 空操作(整页陈旧 = 页 63 图案)与提前清零(前段新后段旧)都会 + * 表现为不一致。 + * 对照组:延迟 50ms 后重读同 3 页——立即读失败而延迟读成功 = 时间窗型故障。 + * 首次失败时记录页头/尾 4B,用于区分 H1 / H2 签名。 + * 影响面:仅擦写最高一块(gd5f_find_run(1) 取最高好块,远离 FTL 活跃区); + * 不写 BBT、不格式化卷。 + * ------------------------------------------------------------------- */ +#ifdef ENABLE_GD5F_PHASE11_TESTS + +#define GD5F_P11_ROUNDS 16 +static const uint32_t s_p11_probe[3] = {0, 21, 43}; + +static void gd5f_phase11(void) +{ + uint32_t blk; + uint32_t page0; + int32_t round, pi, p, i; + int32_t ret; + int32_t storm_fail = 0; + int32_t imm_fail = 0, ctl_fail = 0; + int32_t imm_bad = 0, ctl_bad = 0; + int32_t first_bad = -1; + + blk = gd5f_find_run(1); + if (blk >= GD5F_TOTAL_BLOCKS) { + GD5F_TEST_CHECK(0, "TC-GD5F-1100 no usable block for repro"); + GD5F_TEST_REPORT("GD5F Phase 11"); + return; + } + page0 = blk * GD5F_PAGES_PER_BLOCK; + + DBG_INFO("=== GD5F Phase 11: 13h latch / WIP repro (%d rounds, block %u) ===", + GD5F_P11_ROUNDS, (unsigned)blk); + + for (round = 1; round <= GD5F_P11_ROUNDS; round++) { + int32_t round_bad = 0; + + /* 风暴:擦除 + 64 页背靠背编程(图案 = i ^ p*0x11 ^ round*0x37) */ + if (gd5f_block_erase(blk) != GD5F_OK) { + storm_fail++; + } + for (p = 0; p < GD5F_PAGES_PER_BLOCK; p++) { + for (i = 0; i < GD5F_PAGE_SIZE; i++) { + s_wbuf[i] = (uint8_t)(i ^ (p * 0x11) ^ (round * 0x37)); + } + gd5f_write_enable(); + if (gd5f_program_load(0, s_wbuf, GD5F_PAGE_SIZE) != GD5F_OK || + gd5f_program_exec(page0 + (uint32_t)p) != GD5F_OK) { + storm_fail++; + } + } + + /* 立即读(零间隔、最坏时序):3 个非相邻页逐字节比对 */ + for (pi = 0; pi < 3; pi++) { + int32_t ok = 1; + uint8_t want; + + ret = gd5f_page_read(page0 + s_p11_probe[pi]); + if (ret != GD5F_OK) { + ok = 0; + } + memset(s_rbuf, 0, GD5F_PAGE_SIZE); + if (gd5f_read_from_cache(0, s_rbuf, GD5F_PAGE_SIZE) != GD5F_OK) { + ok = 0; + } + for (i = 0; ok && i < GD5F_PAGE_SIZE; i++) { + want = (uint8_t)(i ^ (s_p11_probe[pi] * 0x11) ^ (round * 0x37)); + if (s_rbuf[i] != want) { + ok = 0; + } + } + if (!ok) { + imm_fail++; + round_bad = 1; + if (first_bad < 0) { + first_bad = round; + DBG_INFO("TC-GD5F-1101 r=%d p=%u IMM mismatch head=%02X %02X %02X %02X " + "tail=%02X %02X %02X %02X", + round, (unsigned)s_p11_probe[pi], + s_rbuf[0], s_rbuf[1], s_rbuf[2], s_rbuf[3], + s_rbuf[GD5F_PAGE_SIZE - 4], s_rbuf[GD5F_PAGE_SIZE - 3], + s_rbuf[GD5F_PAGE_SIZE - 2], s_rbuf[GD5F_PAGE_SIZE - 1]); + } + } + } + + /* 对照组:延迟 50ms 后重读同 3 页(区分时间窗型故障) */ + HAL_Delay(50); + for (pi = 0; pi < 3; pi++) { + int32_t ok = 1; + uint8_t want; + + ret = gd5f_page_read(page0 + s_p11_probe[pi]); + if (ret != GD5F_OK) { + ok = 0; + } + memset(s_rbuf, 0, GD5F_PAGE_SIZE); + if (gd5f_read_from_cache(0, s_rbuf, GD5F_PAGE_SIZE) != GD5F_OK) { + ok = 0; + } + for (i = 0; ok && i < GD5F_PAGE_SIZE; i++) { + want = (uint8_t)(i ^ (s_p11_probe[pi] * 0x11) ^ (round * 0x37)); + if (s_rbuf[i] != want) { + ok = 0; + } + } + if (!ok) { + ctl_fail++; + ctl_bad = 1; + } + } + + if (round_bad) { + imm_bad++; + } + if (round == 1 || (round & 3) == 0) { + DBG_INFO("TC-GD5F-1101 progress r=%d/%d storm_fail=%d imm_fail=%d ctl_fail=%d", + round, GD5F_P11_ROUNDS, storm_fail, imm_fail, ctl_fail); + } + } + + GD5F_TEST_CHECK(storm_fail == 0, + "TC-GD5F-1100 storm ops: erase/program all OK (fail=%d)", storm_fail); + GD5F_TEST_CHECK(imm_bad == 0, + "TC-GD5F-1101 immediate read after storm: %d/%d bad rounds (pages=%d first=%d)", + imm_bad, GD5F_P11_ROUNDS, imm_fail, first_bad); + GD5F_TEST_CHECK(ctl_fail == 0, + "TC-GD5F-1102 delayed(50ms) read: %d/%d bad rounds (pages=%d)", + ctl_bad, GD5F_P11_ROUNDS, ctl_fail); + GD5F_TEST_REPORT("GD5F Phase 11"); +} +#endif /* ENABLE_GD5F_PHASE11_TESTS */ + /* ------------------------------------------------------------------- * 入口:按启用的阶段顺序执行并汇总 * ------------------------------------------------------------------- */ @@ -829,6 +974,9 @@ void gd5f_test_run(void) #ifdef ENABLE_GD5F_PHASE10_TESTS gd5f_phase10(); #endif +#ifdef ENABLE_GD5F_PHASE11_TESTS + gd5f_phase11(); +#endif GD5F_TEST_REPORT("GD5F2GQ5UE Tests"); DBG_INFO("########## GD5F2GQ5UE Test Done ##########"); diff --git a/test/test_config.h b/test/test_config.h index 3202eb13..3282ed89 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -66,6 +66,7 @@ extern "C" { #define ENABLE_GD5F_PHASE2_TESTS /* BBT 读取与 RAM 内标记(206/207 落盘另受门控) */ #define ENABLE_GD5F_PHASE7_TESTS /* DMA 边界与守卫(701~703/705 写擦受门控;704 只读,日常回归亦跑) */ #define ENABLE_GD5F_PHASE10_TESTS /* 越界与 size=0 参数校验(1003/1004 写入另受门控) */ +//#define ENABLE_GD5F_PHASE11_TESTS /* 13h 锁存复现实验(2026-09-21 已完成:16 轮×48 读零复现;仅擦写最高一块,可随时重开) */ #ifdef ENABLE_DESTRUCTIVE_TESTS /* --- 破坏性阶段:写/擦页与块、格式化、FatFS 写入 --- */