2575177e81
纯改名,检查行为一个字没动:规则逻辑、阈值、豁免形状全部原样。 改名原因(用户一句「R4 到底是什么」问出来的):R1~R4 是我在 check_bsp_err.py 注释里 自造的序号,而这个裸形在本仓另有含义——R1~R8 已在 scripts/id_registry.json 登记为 "专项风险"的文档内编号(保护规划与看门狗的风险表都在用),带连字符的那一族又是审查 报告的可靠性 / 内存优化项编号(编号规范 §1 的"连字符承载语义"一行正是这么写的)。 **同一个裸形四种意思**。最刺人的一点:编号门禁**不会报**这种撞法,因为编号规范 §4.3 已把这些裸形声明为"同号不同义"、走 collides_with 白名单,校验 4 不计事故——同一天里 我起的另一组自造号被当场拦下、这组代号却一路绿灯,差别不在我做对了什么,而在白名单 挡的是"事故"、不是"可读性"。 新名同时是基线键的第二字段(格式 文件|标签|名字): · err-type 错误通道不外泄(对外接口不拿 HAL_StatusTypeDef / bool 当成败) · timeout-name 超时宏不取负值 · return-doc 公开函数必须写「返回值:」 · consume-result 返回值不得裸丢弃(须接住或显式 (void)) 老名对照:R1=err-type、R2=timeout-name、R3=return-doc、R4=consume-result。 同步面:脚本里的规则码与 38 条基线键的字段**一起换**(中途不跑 --prune,否则改名会被 读成"38 条全还清 + 38 条新账"),换完 --emit-baseline 复核仍是 38/38/0、RC=0。现状类 文本全部换新名:AGENTS.md、pre-commit使用说明.md §2.27(新增"检查项叫什么"一行含老新 对照与撞号说明)、bsp_err.h 边界纪律、报告 §0.4 / §9 / 附录 A,以及代码注释里的 5 处 (gd5f2gq5ue.c/.h、nand_ftl.h、ui_menu_tab.h)。已提交的变更记录行里的老名按"冻结历史 不回改"处理,以 §2.27 对照行为准。ui_menu_tab.h 带 BOM,Edit 又剥掉、已按原样补回。 写说明文字时又被编号门禁拦了一次:我为了举例"老名撞车",在那段话里写了两个编号字面, 被判"未紧邻文档名 / 未登记前缀"共 3 类新账——连批评自造编号的段落本身都在自造编号。 改法是不写号、只描述形态并指向权威条目。 验证:@build **0 错 0 警告**,Program Size 与上一笔**逐字段相同**(Code 213,676、RO 18,860、RW 1,188、ZI 131,500)→ 证明确为注释与文案,故不跑 @test(无指令变化); pre-commit run 全绿 **17 项 Passed**;check_doc_links --all --sections-strict 147 篇 0 问题(中途我自己写的 §4.3 没紧邻文档名,被判成本文自引用,已补全路径);9 个文件字节 卫生 0 异常(UI 头 BOM 仍在、无 CR、无 U+FFFD、均有尾换行)。
144 lines
9.6 KiB
C
144 lines
9.6 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 的 **consume-result** 检查机械化(2026-10-23)。
|
||
* 为什么不能指望工具,两条实测负结果:① ARMCC v5 对 __attribute__((warn_unused_result))
|
||
* **完全不生效**——凌晨在 rs485_transmit 上做过实验,属性在位、调用点裸调不接返回值,构建
|
||
* 仍 0 错 0 警告;② **cppcheck 也照不出来**——按本仓自己的 --enable 跑 gd5f2gq5ue.c,对 14 处
|
||
* 丢弃 HAL_SPI_Transmit/Receive 返回值、以及"未初始化局部量直接进 P_FAIL 判定"**一条不报**
|
||
* (它假定被调函数会写出参,而失败路径根本不写)。所以"丢弃底层传输的返回值"今天没有任何
|
||
* 工具会响,只能靠脚本 + 书写约定;consume-result 上线当天就照出 37 处存量丢弃,其中一处是真窟窿:
|
||
* bbt_rebuild 的 ECC 重开被丢弃(详见 ../测试说明/存储维护与容量恢复.md §4.3)。
|
||
* 5. 重试由调用方决定:必须有上限、必须喂看门狗、不得跨越就绪屏障盲目等。本头文件
|
||
* 不提供 retry helper——一个通用 while 重试包装太容易被拿去包住无限等待;每个日志
|
||
* 调用点内联约 216 B 栈的教训在前,栈预算不欢迎「顺手加一层」。
|
||
*/
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* BSP_ERR_H */
|