Files
STM32F4-Base/Drivers/BSP/bsp_err.h
T
wandering1 d9b887c0af feat(S-5 R4): 返回值不得裸丢弃成为机械检查,并修掉照出的第一处 ECC 静默降级
B 的第一步(加规则)与第二步(修最小最值的那处)。用户裁定"B 按你说的进行"。

**为什么只能项目侧自己做**(两条实测负结果,已写进 §2.27 与 bsp_err.h 边界纪律 4):
· ARMCC v5 对 __attribute__((warn_unused_result)) 不生效(凌晨在 rs485_transmit 上实测:属性
  在位、调用点裸调不接返回值,构建仍 0 错 0 警告);
· cppcheck 也照不出来——按本仓自己的 --enable=warning,style,performance,portability 跑
  gd5f2gq5ue.c,对 14 处丢弃 HAL_SPI_* 返回值、以及"未初始化局部量直接进 P_FAIL 判定"
  **一条不报**(它假定 gd5f_read_status(&status) 会写出参,而失败路径根本不写)。
结论:**丢弃底层传输的返回值,今天没有任何工具会响**。

**R4 上线即照出 37 条存量丢弃**:gd5f2gq5ue.c 29(14 处 HAL + 15 处自家原语)、net_socket.c 8、
ui_net_sink.c 4(net_close)、rs485.c 2、nand_ftl.c 1。按增量拦截老规矩入基线、只减不增——
这是覆盖面扩大新暴露的历史债,不是新增违规;基线 19 → 1 → **38**。

**R4 自己被修三次(逐条记下,都是"规则比我想的更脆")**:
① 首版漏判"裸调用行尾带注释"这种写法(正则的 \s*$ 卡死),而那恰是最常见的"带说明的丢弃"
   → 补可选注释分支,并做 6 条正/反例自检;
② 把 net_set_errno 八处误判成丢弃(它是 setter,返回 int 是状态回声不是调用成败)→ **按真实
   反例把规则收窄**(谓词 + setter 两类豁免),而不是塞进基线洗掉假阳性;
③ must-use 名单只从**头文件**取,gd5f_set_feature 这类 static 原语整个在视野外——而下面那处
   真窟窿恰恰是 static、靠人工才发现 → 名单扩到 .c 内 static 定义(同一套启发式),补完立刻
   又照出 gd5f_bbt_save#1:**运行时标坏后 BBT 落盘结果被丢弃 = 坏块记账可能没持久化**,已入
   基线记为待还,本步不动行为。
另修一处自己的排版误判:R3 原本要求注释块出现字面"返回值",规范 V1.0 允许列对齐写法
「返 回 值:」→ 改为忽略空格匹配(门禁不该规定排版)。

**修掉的第一处真窟窿**:bbt_rebuild 的注释写着"无论扫描成败都须恢复 ECC,否则后续 I/O 在无
ECC 下运行",而它下面 gd5f_set_feature(0xB0,…) + gd5f_wait_busy(100) **返回值全丢**:SPI 一抖
ECC 就保持关闭,且 gd5f_check_ecc() 在 ECC 关闭时恒读"无 ECC 错"——整条链再没有任何信号。
补法复用 init 路径的 E-10(写完回读 B0h 验 ECC_EN 位真生效,"写了但没生效"与写失败同罪),
并把"关 ECC 失败"改成**立即中止**(此时 ECC 仍开着,是安全侧;带着 ECC 去扫 spare 反而会把
厂标好块误判成坏块,"重建 BBT"变成制造坏块)。恢复失败时按 (void)gd5f_bbt_load() 回读盘上
副本,避免 RAM 留着"比盘上更新"的表而下次上电又退回旧表。

**一条定位纠正(差点把修复记大)**:查链接映射发现 Removing gd5f2gq5ue.o
(i.gd5f2gq5ue_bbt_rebuild), (604 bytes)——该函数在产品镜像里**根本没被链接**(产品构建无调用
者),只由 ENABLE_STORAGE_RECOVERY_TESTS 的恢复构建经 testTask 调用。所以本次修的是**恢复流程
自身的安全**(跑恢复时若 ECC 没恢复,设备会带着"已恢复"的结论在无保护状态下继续跑),不是
现场日常路径;相应地镜像 Code 的 +28 B 也不是它带来的(未逐字节追,线索是
gd5f_private_get_feature 由单调用点变多调用点、内联决策可能变化)。验证因此不能只跑产品构建:
临时打开 TEST_SUITE_STORAGE + ENABLE_STORAGE_RECOVERY_TESTS 编一次(0 错 0 警告且该函数不再
出现在 Removing 列表),随即还原两处开关并重编产品构建核对(test/board 无残留差异)。

**编号门禁把我自己的自造编号拦下了**:我起初用 B-1/B2/B3/B4 标记这批缺陷,id-check 报"未登记
前缀"13 处(且 B2/B3/B4 会与真实里程碑号撞号)→ 全部改写成平实语言。这是那条"注释与文档里
不许用没人检索得到的内部阶段编号"的规则第一次在提交前拦住我,规则是对的。

验证:@build 0 错 0 警告,Code 213,648 → **213,676**、RO 18,860、ER_IROM1 **232,536 B
  / 22.2%**
(ROM 合计 233,724 B;RAM 逐字段不变);ram_report 重生成 + ram_baseline.py --update + 配图常量
同步(mem-numbers Passed);@test **PASS**(到 net init: OK 682 ms、[ERR] 0);R4 两处**探针
实证**(临时加一条裸调用 → 立刻成为新增并 RC=1;还原 → 38/38/0);--prune 点名本次还掉的 2 条
已删;pre-commit run 全绿 15 Passed;check_doc_links --all --sections-strict 147 篇
  0 问题;
id-check --strict 增量拦截通过。

文档:AGENTS.md 与 .pre-commit-config.yaml 的 bsp-err 口径(四条规则、基线 38);
pre-commit使用说明.md §2.27(R4 全量口径 + 两条负结果 + 名单来源与豁免形状 + 单行限度);
bsp_err.h 边界纪律 4;报告 §0.4 / §5.1 / §9 / 附录 A + 变更记录;存储维护与容量恢复.md 新增
§4.3(运维判据:出现 ECC restore FAILED / ECC not enabled 即本次恢复**不算完成**,须断电重启
重走 init,不许接着 nand_ftl_format() 当作已修好;并记明该函数产品镜像不链接)——顺带修该文
结构缺陷:原 ## 5. 变更记录 误插在 §4.1 与 §4.2 之间,会让 §4.2 变成 §5 的子节。
2026-10-23 09:15:41 +08:00

144 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:BSP 统一错误与等待约定
* 模块功能:给 Drivers/BSP 下的所有驱动一个共用的返回码语义与等待时长语义,
* 使「这次失败能不能重试」成为跨模块可判定的信息,而不是每个模块各自猜。
* 适用平台:STM32F4 系列(ARMCC v5);本头文件不依赖 HAL,模拟器/主机侧同样可编译。
* 作者:王建锋
* 创建日期:2026-10-23
* 修改记录:
* 2026-10-23 王建锋 创建:错误码 + 可重试判定 + 等待值契约(首个试点 RS485;同日挂 bsp-err 钩子)
*/
#ifndef BSP_ERR_H
#define BSP_ERR_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* ============================================================================
* 一、错误码:0 = 成功,负值 = 失败
* ==========================================================================*/
/*
* 为什么 0 必须是成功:本仓现存调用点绝大多数已经写成 != 0 / == 0(实测跨模块调用
* 点约 798 处),反向约定要把每个判据都改一遍,纯赔本。
*
* ⚠ **一个数量与一个错误码不许挤在同一个返回值里**(面向新 API 的原则)。
* 现存两族,都**有意保留**、都已核对:
* · net_*(返回字节数,0=对端关闭,-1=错误,-2=超时无数据)——刻意的 BSD 形状,
* 已裁定不纳入(见文末边界纪律 3);
* · tpafe5160_snapshot() / adc_input_poll()(1=取到新帧,0=无新帧,BSP_ERR_PARAM=参数错)
* ——**0 不是错误**,是"这一拍没有新数据",套成败码会把慢消费者的正常路径变成失败。
* 反例订正:gd5f2gq5ue_read/write/erase 核过实现与头注释是"0=成功、其它=错误码",
* **不是**字节数返回,别再拿它当反面例子(本头文件初稿错过这个错)。
* 新 API 要报"做了多少"就走出参:bsp_err_t xxx(..., size_t *p_done)。
* 为什么值得写死这条:三态返回(>0 / 0 / <0)要求调用方把三种形态都判到,而实测调用点
* 大多是"先赋值再判 != 0"——第三种形态就是这么被吞掉的。
*/
typedef int bsp_err_t;
#define BSP_OK ((bsp_err_t)0) /* 成功 */
#define BSP_ERR_PARAM ((bsp_err_t)-1) /* 参数非法:指针空、长度越界 */
#define BSP_ERR_STATE ((bsp_err_t)-2) /* 调用时机不对:未初始化、状态机不允许 */
#define BSP_ERR_TIMEOUT ((bsp_err_t)-3) /* 超时 —— 可重试 */
#define BSP_ERR_IO ((bsp_err_t)-4) /* 总线/介质读写失败 */
#define BSP_ERR_NO_DEVICE ((bsp_err_t)-5) /* 芯片不在位、无响应(上电时序或硬件) */
#define BSP_ERR_BUSY ((bsp_err_t)-6) /* 对端或本侧忙 —— 可重试 */
#define BSP_ERR_CRC ((bsp_err_t)-7) /* 校验失败:ECC / CRC / 和校验 */
#define BSP_ERR_NO_SPACE ((bsp_err_t)-8) /* 容量或块池耗尽 */
#define BSP_ERR_DENIED ((bsp_err_t)-9) /* 被策略拒绝:写保护、鉴权、门未开 */
#define BSP_ERR_PARTIAL ((bsp_err_t)-10) /* 部分完成,配合 *p_done 判断做到哪一步 */
/*
* 函数功能:判断一个失败码是否「重试可能成功」
* 入口参数:e - 待判定的 bsp_err_t
* 返回值:非 0 = 可重试;0 = 重试也没用(参数、状态、策略、无器件这类确定性失败)
* 限定条件:只用于已确认失败的码(传 BSP_OK 得 0,不算错)
* 函数说明:这才是本头文件真正值钱的一条。现状是把「可重试」写在注释里——
* gd5f2gq5ue.c 的返回值注释就写着「GD5F_BUSY_TIMEOUT(-2) - 超时(可重试)」,
* 语义在,但跨模块不可判定:上层想统一退避,就得为每个驱动各写一遍 if。有了这条,
* 「超时/忙退避、确定性失败立刻放弃」可以只写在一处。不要据此无上限重试:
* 上限、退避节奏、喂看门狗都由调用方负责(见文末约定)。
*/
#define BSP_ERR_CAN_RETRY(e) (((e) == BSP_ERR_TIMEOUT) || ((e) == BSP_ERR_BUSY))
/* ============================================================================
* 二、等待时长契约:把「这个数到底是多久」写死
* ==========================================================================*/
/*
* 实测现状(同形不同义,这就是本契约存在的全部理由):
* · SD2506_I2C_TIMEOUT_MS 100、CH395F_SPI_BYTE_TIMEOUT_MS 100U
* 真的毫秒:喂 HAL 的 tick 域,本仓 HAL 时基 1ms。
* · osWaitForever 与 HAL_MAX_DELAY 同为 0xFFFFFFFFU
* 值相同、域不同:一个进 CMSIS-RTOS2、一个进 HAL。于是「把上层拿到的超时值直接
* 转手传给另一层」这种代码既不报错也不变慢,直到某天真等死。
* · GD5F_SPI_TIMEOUT 100(**核对后的准确说法,我先前记成"自旋圈数"是错的**)
* 它其实也是毫秒(全部用法都是 HAL_* 超时参数或 HAL_GetTick() 差值,逐行核过),
* 真正的问题是**名字没带 _MS 后缀**:读代码的人必须回实现里查一遍才知道单位,
* 而同名的 *_TIMEOUT 在别的模块里又是返回码——单位与用途都靠"点进去看"。
* · GD5F_BUSY_TIMEOUT、TPAFE5160_BUSY_TIMEOUT = -2(**2026-10-23 已消**:前者改名
* GD5F_WAIT_EXPIRED(值不变),后者收编成 BSP_ERR_TIMEOUT;留此记录是因为
* "时长宏与返回码共用一个名字形状"这类坑只写规则不会自己消失,得留个实证锚点)
*
* 因此约定:
* 一、公开 API 的超时参数一律 uint32_t 毫秒;
* 二、要无限等就显式传 BSP_WAIT_FOREVER,不许写 0xFFFFFFFFU、-1 或 0(0 是不等);
* 三、自旋圈数型超时不许叫 *_TIMEOUT_MS,命名须含 LOOP/COUNT 且注释写明非时间量;
* 返回码一律不许叫 *_TIMEOUT,改返回 BSP_ERR_TIMEOUT。
*
* ⚠ 两条硬纪律(AGENTS 铁律,这里只复述到最近的使用点):
* · 内核启动前不得传 BSP_WAIT_FOREVER:osKernelInitialize 之前任何阻塞调用都会挂;
* · 长等待与重试循环必须在循环内喂看门狗(本仓 IWDG 有 required_mask 机制)。
*/
#define BSP_WAIT_NONE ((uint32_t)0U) /* 不等待,立即返回 */
#define BSP_WAIT_FOREVER ((uint32_t)0xFFFFFFFFU) /* 无限等待,仅限任务上下文 */
/*
* 函数功能:把毫秒表达成超时入参(本仓 tick = 1ms,故等价)
* 入口参数:ms - 毫秒数
* 返回值:可直接传给等待类 API 的 uint32_t
* 限定条件:仅当 configTICK_RATE_HZ = 1000 时与 tick 数等价
* 函数说明:留着它的理由是换算点必须只有一处——哪天 tick 改了只改这里,而不是去
* 各驱动里翻 0xFFFFFFFF 和裸数字。
*/
#define BSP_WAIT_MS(ms) ((uint32_t)(ms))
/* ============================================================================
* 三、边界纪律(写在这里是为了说清什么算「不该改」)
* ==========================================================================*/
/*
* 1. 第三方的码不改、不吞、就地映射:HAL_StatusTypeDef、dhara_error_t、FRESULT、
* CH395F_ERR_*(芯片寄存器原生码)、FTP 的 226/425/450/550 协议码,全部保留原样,
* 只在跨越我们模块边界那一行映射成 bsp_err_t。它们是外部契约或协议本身,改名等于
* 伪造语义。映射函数由各驱动自己写一个 static——本头文件刻意不 include HAL,
* 以保持主机侧可编译。
* 2. 公开 API 面不得返回 HAL_StatusTypeDef 或 bool 表示成败:bool 把「为什么失败」整条
* 丢掉,HAL 枚举把 HAL 焊进调用方的 include 链。
* 3. net_* 的 BSD 兼容层保持 BSD 形状(返回字节数 + 每任务 errno 槽),**不进本约定**
* (用户确认 2026-10-23):它的调用方要的就是那个形状,模拟器的 Winsock 后端也照同一
* 形状实现;归一只到芯片层 ch395f_*。要收口的是"芯片码到 bsp_err_t"这一层,不是把
* BSD 形状改成另一套——那只会让 net_* 的调用方在每个判据上多绕一道。
* 4. **丢弃返回值必须写 (void),或由调用点接住**——本条已由 bsp-err 的 **R4** 机械化(2026-10-23)。
* 为什么不能指望工具,两条实测负结果:① ARMCC v5 对 __attribute__((warn_unused_result))
* **完全不生效**——凌晨在 rs485_transmit 上做过实验,属性在位、调用点裸调不接返回值,构建
* 仍 0 错 0 警告;② **cppcheck 也照不出来**——按本仓自己的 --enable 跑 gd5f2gq5ue.c,对 14 处
* 丢弃 HAL_SPI_Transmit/Receive 返回值、以及"未初始化局部量直接进 P_FAIL 判定"**一条不报**
* (它假定被调函数会写出参,而失败路径根本不写)。所以"丢弃底层传输的返回值"今天没有任何
* 工具会响,只能靠脚本 + 书写约定;R4 上线当天就照出 37 处存量丢弃,其中一处是真窟窿:
* bbt_rebuild 的 ECC 重开被丢弃(详见 ../测试说明/存储维护与容量恢复.md §4.3)。
* 5. 重试由调用方决定:必须有上限、必须喂看门狗、不得跨越就绪屏障盲目等。本头文件
* 不提供 retry helper——一个通用 while 重试包装太容易被拿去包住无限等待;每个日志
* 调用点内联约 216 B 栈的教训在前,栈预算不欢迎「顺手加一层」。
*/
#ifdef __cplusplus
}
#endif
#endif /* BSP_ERR_H */