d9b887c0af
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 的子节。
144 lines
9.5 KiB
C
144 lines
9.5 KiB
C
/*
|
||
* 模块名称: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 */
|