77779d49d6
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机 OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批 预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block (口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng 反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定 内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按 is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。 记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在 defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE); 事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身 (一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。 为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核, 丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。 为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费 两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过; RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同 码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。 实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B; RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS, protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→ 164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34, 既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、 不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页 连跑两次逐字节一致;字库 diff 空。 顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽 16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前 靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值 全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py 模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。 未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行; LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND), 已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
423 lines
18 KiB
C
423 lines
18 KiB
C
/*
|
||
* 模块名称:UI 数据快照存储(ui_data)
|
||
* 模块功能:三通道快照(实时量 rt / 参数 par / 装置信息 info)的存储、发布盖章与
|
||
* 只读访问——本模块是纯内存叶子:不取数、不格式化、不判断业务含义、
|
||
* 不碰 LVGL、不 include 任何 App 头(职责边界与依赖方向 =
|
||
* Drivers/BSP/UI/ui_data.h 文件头与
|
||
* docs/UI设计/UI_数据供给设计说明.md §3.2)
|
||
* 并发模型:单写者同上下文——唯一发布者 App/app/ui_data_feed.c 与全部页面钩子都在
|
||
* uiTask 内串行执行,故无锁、无双缓冲、无 seqlock;将来真出现第二个
|
||
* 发布者,保护收在本模块内部(双槽 + 每槽 seq + 单字节活动下标,照
|
||
* App/algo/phasor.c 双区先例),页面代码不受影响
|
||
* (判据 docs/UI设计/UI_数据供给设计说明.md §5.1)
|
||
* 写路径(定值整定请求队列)同理:生产者是页面"保存"按键钩子、消费者是
|
||
* App 侧节拍里的 ui_data_param_drain(),两者同在 uiTask → 环形队列照旧
|
||
* 无锁;若将来出现第二个投递上下文,唯一要做的是给 request/drain 两侧
|
||
* 各加一把互斥(队列本身是单字 head/cnt 推进,页面侧不会看到半成品)
|
||
* 模块功能补充:除三通道快照外,本模块还持有定值写路径的请求队列与状态字
|
||
* (ui_data_param_request/state/drain)——本层只做"排队 + 状态机",
|
||
* 不判断值域、不碰 prot_set、不碰文件系统(值域与写入事实经
|
||
* ui_data_param_ops_t 由 App 侧注入,理由见 ui_data.h 写路径节)
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-10-25
|
||
* 修改记录:
|
||
* v1.0 2026-10-25 王建锋 创建(道E task-14)
|
||
* v1.1 2026-10-26 DTU 项目组 道F task-1:新增定值写路径环形队列(深度 4、满返 -1)
|
||
* 与状态机(IDLE/WAIT/OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY),
|
||
* 执行经注入函数表回调 App 侧;参数帧新增 cal_block
|
||
* 字段随 publish 原样入存(本层不解释含义)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_data.h"
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* 三通道存储(上电全零 = "尚无首帧",与 ui_data_init() 后状态一致) */
|
||
static ui_data_rt_t s_rt;
|
||
static ui_data_par_t s_par;
|
||
static ui_data_info_t s_info;
|
||
|
||
/*
|
||
* 发布计数:seq 由本层盖章,与入帧携带的 seq 无关——入帧的该字段被覆盖。
|
||
* 读侧"有无首帧"的判据是计数为 0,不是帧内 valid:valid 是"这一帧有没有有效
|
||
* 数据"(写侧可能发布一帧全无效的真值),两者语义不同,混用会让"首帧之前"
|
||
* 与"首帧但无数据"在页面上显示成同一种态。
|
||
*/
|
||
static uint32_t s_rt_cnt;
|
||
static uint32_t s_par_cnt;
|
||
static uint32_t s_info_cnt;
|
||
|
||
/* ── 写路径:定值整定请求队列(深度 UI_DATA_PARAM_QD 的静态环形数组)──────── */
|
||
|
||
/*
|
||
* mask 合法位集合 = 低 UI_DATA_SET_N 位(40 项 → 0x000000FFFFFFFFFF)。
|
||
* 用"全 1 右移"而不是"1 左移 40 位再减 1":前者在 UI_DATA_SET_N == 64 时仍是
|
||
* 良定义(移 0 位),后者会退化成立即未定义行为(移位数 == 位宽)。
|
||
* 项数一旦超过 64,下面的编译期守卫当场拦下——位图放不下就是表示法该换,
|
||
* 绝不能静默把高 24 项变成"投不进去也不报错"
|
||
*/
|
||
#define UI_DATA_PARAM_MASK_VALID ((uint64_t)(~(uint64_t)0u) >> (64u - UI_DATA_SET_N))
|
||
|
||
typedef char ui_data_chk_mask_t[((uint32_t)UI_DATA_SET_N <= 64u) ? 1 : -1];
|
||
|
||
/* 环形队列:head = 出队下标,cnt = 在队条数(0..UI_DATA_PARAM_QD)
|
||
* 为什么用 head+cnt 而不是 head+tail:cnt 直接就是"还有几条没落定",
|
||
* state() 判 WAIT 不必再做 (tail - head) 取模减法,少一处可算错的地方 */
|
||
static ui_data_param_req_t s_pq[UI_DATA_PARAM_QD];
|
||
static uint8_t s_pq_head;
|
||
static uint8_t s_pq_cnt;
|
||
|
||
/* 写路径状态字与越界位图(页面只读;int/uint64 单字读写在 M4 上不撕裂) */
|
||
static int s_pq_state = UI_DATA_PARAM_ST_IDLE;
|
||
static uint64_t s_pq_err_mask;
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:快照层初始化——三通道清零(valid=0、seq=0)+ 写路径队列复位,
|
||
* 页面即按"无首帧/未投递"渲染
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:内核启动前可调(纯内存操作,零 OS 依赖);可重复调用(幂等)
|
||
* 函数说明:静态存储上电本已清零,本函数的意义是运行期复位(例如"恢复出厂后
|
||
* 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态。
|
||
* 写路径一并丢弃在队请求是**有意**的:旧请求带的是旧配置下的编辑叠层,
|
||
* 复位后还把它落进 prot_set,等于拿陈旧草稿覆盖新装载的定值
|
||
*/
|
||
void ui_data_init(void) {
|
||
(void)memset(&s_rt, 0, sizeof(s_rt));
|
||
(void)memset(&s_par, 0, sizeof(s_par));
|
||
(void)memset(&s_info, 0, sizeof(s_info));
|
||
s_rt_cnt = 0u;
|
||
s_par_cnt = 0u;
|
||
s_info_cnt = 0u;
|
||
(void)memset(s_pq, 0, sizeof(s_pq));
|
||
s_pq_head = 0u;
|
||
s_pq_cnt = 0u;
|
||
s_pq_state = UI_DATA_PARAM_ST_IDLE;
|
||
s_pq_err_mask = 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:发布一帧实时量快照(覆盖存量并给 seq 盖章 +1)
|
||
* 入口参数:a_snap - 源帧(本层整帧拷入自有存储,调用方可复用其栈/静态空间)
|
||
* const ui_data_rt_t *
|
||
* 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int
|
||
* 限定条件:唯一发布者 = App/app/ui_data_feed.c,uiTask 上下文
|
||
* 函数说明:seq 由本层盖章(入帧 seq 被忽略);其余字段原样入存,本层不做任何
|
||
* 业务判断与格式化
|
||
*/
|
||
int ui_data_publish_rt(const ui_data_rt_t *a_snap) {
|
||
if (a_snap == NULL) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(&s_rt, a_snap, sizeof(s_rt));
|
||
s_rt_cnt += 1u;
|
||
s_rt.seq = s_rt_cnt;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:发布一帧参数快照(覆盖存量并给 seq 盖章 +1)
|
||
* 入口参数:a_snap - 源帧 const ui_data_par_t *
|
||
* 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int
|
||
* 限定条件:同上;发布节拍 = 定值校验和变化才发
|
||
* (docs/UI设计/UI_数据供给设计说明.md §5.2)
|
||
*/
|
||
int ui_data_publish_par(const ui_data_par_t *a_snap) {
|
||
if (a_snap == NULL) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(&s_par, a_snap, sizeof(s_par));
|
||
s_par_cnt += 1u;
|
||
s_par.seq = s_par_cnt;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:发布一帧装置信息快照(覆盖存量并给 seq 盖章 +1)
|
||
* 入口参数:a_snap - 源帧 const ui_data_info_t *
|
||
* 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int
|
||
* 限定条件:同上
|
||
*/
|
||
int ui_data_publish_info(const ui_data_info_t *a_snap) {
|
||
if (a_snap == NULL) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(&s_info, a_snap, sizeof(s_info));
|
||
s_info_cnt += 1u;
|
||
s_info.seq = s_info_cnt;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读最新一帧实时量快照(整帧拷给页面私有缓存)
|
||
* 入口参数:a_out - 输出缓冲(非 NULL) ui_data_rt_t *
|
||
* 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动,页面显示 "--");
|
||
* -2 - 参数无效(NULL) int
|
||
* 限定条件:页面钩子上下文(uiTask);勿在逐行 refresh 里无条件整帧拷贝,
|
||
* 先比 ui_data_rt_seq()(用法见 Drivers/BSP/UI/ui_data.h 文件头)
|
||
*/
|
||
int ui_data_rt(ui_data_rt_t *a_out) {
|
||
if (a_out == NULL) {
|
||
return -2;
|
||
}
|
||
if (s_rt_cnt == 0u) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(a_out, &s_rt, sizeof(s_rt));
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读最新一帧参数快照
|
||
* 入口参数:a_out - 输出缓冲(非 NULL) ui_data_par_t *
|
||
* 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动);-2 - 参数无效 int
|
||
* 限定条件:同上;定值页同样用 ui_data_par_seq() 门控页面私有缓存
|
||
*/
|
||
int ui_data_par(ui_data_par_t *a_out) {
|
||
if (a_out == NULL) {
|
||
return -2;
|
||
}
|
||
if (s_par_cnt == 0u) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(a_out, &s_par, sizeof(s_par));
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读最新一帧装置信息快照
|
||
* 入口参数:a_out - 输出缓冲(非 NULL) ui_data_info_t *
|
||
* 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动);-2 - 参数无效 int
|
||
* 限定条件:同上
|
||
*/
|
||
int ui_data_info(ui_data_info_t *a_out) {
|
||
if (a_out == NULL) {
|
||
return -2;
|
||
}
|
||
if (s_info_cnt == 0u) {
|
||
return -1;
|
||
}
|
||
(void)memcpy(a_out, &s_info, sizeof(s_info));
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:查实时帧发布序号(页面缓存失效判据)
|
||
* 入口参数:无
|
||
* 返回值:当前 seq;从未发布 = 0 uint32_t
|
||
* 限定条件:无(只读单字,任意上下文安全)
|
||
* 函数说明:轻量查询——只读一个 uint32,可逐行调用;序号只在发布时 +1,页面
|
||
* 据此把"整帧拷贝"从每帧每行降到只在换帧时一次
|
||
*/
|
||
uint32_t ui_data_rt_seq(void) {
|
||
return s_rt.seq;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:查参数帧发布序号
|
||
* 入口参数:无
|
||
* 返回值:当前 seq;从未发布 = 0 uint32_t
|
||
* 限定条件:无
|
||
*/
|
||
uint32_t ui_data_par_seq(void) {
|
||
return s_par.seq;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:查装置信息帧发布序号
|
||
* 入口参数:无
|
||
* 返回值:当前 seq;从未发布 = 0 uint32_t
|
||
* 限定条件:无
|
||
*/
|
||
uint32_t ui_data_info_seq(void) {
|
||
return s_info.seq;
|
||
}
|
||
/*
|
||
* 函数功能:投递一条定值写入请求(页面"保存"行的唯一出口——只投递,不执行)
|
||
* 入口参数:a_req - 请求(本层整条拷入环形队列,调用方可复用其栈/叠层空间)
|
||
* const ui_data_param_req_t *
|
||
* 返回值:0 - 已入队(状态转 WAIT);或 mask 为 0 的空请求(无改动可保存,
|
||
* 状态直接转 OK,不占槽位)
|
||
* -1 - 队列满(状态转 ERR_BUSY,在队请求一条不动);
|
||
* -2 - 参数无效:NULL 或 mask 含 UI_DATA_SET_N 以上的位
|
||
* (状态与队列都不动——把调用方的编程错误落成屏上"错误"会误导现场) int
|
||
* 限定条件:uiTask 上下文(页面按键钩子);与 drain 同上下文串行 → 无锁
|
||
* 函数说明:本层不校验值域(越界与否属执行侧事实,落在 drain 的整批预检),
|
||
* 不 include App 头、不碰 prot_set、不碰 FatFs(UI 直写定值会把校验和
|
||
* 打成中间态、直写盘会撞 defaultTask 单写者纪律——契约 §6.1)
|
||
* 空请求按成功处理是刻意的:保存行 ENTER 时"一个都没改"是正常操作,
|
||
* 报 ERR 会让用户以为设备有问题
|
||
*/
|
||
int ui_data_param_request(const ui_data_param_req_t *a_req) {
|
||
uint8_t slot;
|
||
|
||
if (a_req == NULL) {
|
||
return -2;
|
||
}
|
||
if ((a_req->mask & ~UI_DATA_PARAM_MASK_VALID) != 0u) {
|
||
return -2;
|
||
}
|
||
if (a_req->mask == 0u) {
|
||
s_pq_state = UI_DATA_PARAM_ST_OK;
|
||
s_pq_err_mask = 0u;
|
||
return 0;
|
||
}
|
||
if (s_pq_cnt >= (uint8_t)UI_DATA_PARAM_QD) {
|
||
s_pq_state = UI_DATA_PARAM_ST_ERR_BUSY;
|
||
return -1;
|
||
}
|
||
|
||
slot = (uint8_t)(((uint32_t)s_pq_head + (uint32_t)s_pq_cnt) % UI_DATA_PARAM_QD);
|
||
(void)memcpy(&s_pq[slot], a_req, sizeof(s_pq[slot]));
|
||
s_pq_cnt += 1u;
|
||
s_pq_state = UI_DATA_PARAM_ST_WAIT;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取写路径状态(页面保存行显示)
|
||
* 入口参数:无
|
||
* 返回值:UI_DATA_PARAM_ST_IDLE / _WAIT / _OK / _ERR_RANGE / _ERR_LOCKED /
|
||
* _ERR_BUSY 之一 int
|
||
* 限定条件:无(只读单字,任意上下文安全)
|
||
* 函数说明:本函数就是 s_pq_state 的直读,不做"pending 优先"之类的二次加工——
|
||
* 三条写路径(投递成功→WAIT、投递被拒→ERR_BUSY、drain 落定→终态或
|
||
* 残留则 WAIT)已把顺序语义收在写侧,读侧再判一次等于两处解释同一件事
|
||
*/
|
||
int ui_data_param_state(void) {
|
||
return s_pq_state;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取最近一条请求的越界项位图(页面高亮越界行)
|
||
* 入口参数:无
|
||
* 返回值:bit i = 1 → 第 i 项越界;最近终态不是 ERR_RANGE 时恒 0 uint64_t
|
||
* 限定条件:无(只读单字)
|
||
* 函数说明:与 ui_data_par_t.bad_mask 两条来源不同(本函数 = 本次提交被预检挡下
|
||
* 的项;bad_mask = prot_set 整表装载/解析留下的越界位图),页面在
|
||
* ERR_RANGE 那一帧用本函数,混用会把上一次装载的越界显示成本次的
|
||
*/
|
||
uint64_t ui_data_param_err_mask(void) {
|
||
return s_pq_err_mask;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:整批预检一条请求(位图内置位的项逐个问执行侧 check())
|
||
* 入口参数:a_ops - 执行侧函数表(调用方已核非 NULL)
|
||
* a_req - 待检请求
|
||
* 返回值:越界项位图(bit i = 第 i 项越界;0 = 本批全部在界内) uint64_t
|
||
* 限定条件:只调 check(),不写任何东西("预检"与"落值"分成两个函数,
|
||
* 正是为了让 all-or-nothing 有地方落脚:整批扫完才决定写不写)
|
||
* 函数说明:一次扫全 40 位而不是"遇到第一个越界就返回"——err_mask 要给页面
|
||
* **全部**越界行反显,只报第一行等于让用户改完一行再撞下一行
|
||
*/
|
||
static uint64_t ui_data_param_scan(const ui_data_param_ops_t *a_ops,
|
||
const ui_data_param_req_t *a_req) {
|
||
uint64_t bad = 0u;
|
||
uint64_t bit = 1u;
|
||
uint8_t i;
|
||
|
||
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
|
||
if ((a_req->mask & bit) != 0u) {
|
||
if (a_ops->check(i, a_req->q10[i]) != 0) {
|
||
bad |= bit;
|
||
}
|
||
}
|
||
bit <<= 1u;
|
||
}
|
||
return bad;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:逐项落值一条请求(调用前 ui_data_param_scan() 已确认整批在界内)
|
||
* 入口参数:a_ops - 执行侧函数表
|
||
* a_req - 待写请求
|
||
* 返回值:0 - 位图内全部写入;-1 - 某项被执行侧拒绝(*p_bad 输出该项) int
|
||
* 限定条件:apply() 的副作用由执行侧定义(App 实现 = prot_set_set_sealed,
|
||
* 每项自带置脏);本函数不回滚已写项——prot_set 没有回滚接口
|
||
* 函数说明:走到这里还失败 = 执行侧与预检不一致(两个真源漂移),理论不可达;
|
||
* 如实报失败项比谎称"整批未写"更诚实,故**可能已有前几项生效**
|
||
*/
|
||
static int ui_data_param_write(const ui_data_param_ops_t *a_ops,
|
||
const ui_data_param_req_t *a_req,
|
||
uint64_t *p_bad) {
|
||
uint64_t bit = 1u;
|
||
uint8_t i;
|
||
|
||
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
|
||
if ((a_req->mask & bit) != 0u) {
|
||
if (a_ops->apply(i, a_req->q10[i]) != 0) {
|
||
*p_bad = bit;
|
||
return -1;
|
||
}
|
||
}
|
||
bit <<= 1u;
|
||
}
|
||
*p_bad = 0u;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:处理一条定值写入请求(App 装配层节拍入口:出队 → 闭锁 → 预检 → 落定)
|
||
* 入口参数:a_ops - 执行侧函数表(locked/check/apply 三者都不得为 NULL)
|
||
* const ui_data_param_ops_t *
|
||
* 返回值:1 - 处理并提交了一条(状态已转终态,或队列仍有残留则转 WAIT);
|
||
* 0 - 队列空(状态与队列都不动);
|
||
* -1 - 参数无效(a_ops 或其成员为 NULL,状态与队列都不动) int
|
||
* 限定条件:uiTask 上下文(App/app/ui_data_feed.c 的 tick 每轮一次);每轮最多
|
||
* 处理一条——整定是低频人工事件,一轮一条已够,且把最坏耗时钉在
|
||
* 2 × 40 次回调(预检一遍 + 写入一遍),不随队列堆积抖动
|
||
* 函数说明:四步顺序即判据(契约 §6.2),判定本体拆成 scan/write 两个静态函数
|
||
* (见其函数说明)——
|
||
* ① 取队首;② locked() → ERR_LOCKED 并**丢弃本条**(闭锁是业务态,不做
|
||
* "排队等解锁",否则屏上 WAIT 无限期挂着,用户以为设备卡死);
|
||
* ③ scan() 任一项越界 → ERR_RANGE 且**一字不写**(all-or-nothing,与
|
||
* prot_set_load_raw 同纪律:半张表落进值区就是"看着保存成功、其实混着
|
||
* 新旧两套定值");④ write() 全成 → OK。
|
||
* ⚠ 顺序不可调:先判闭锁再判越界——闭锁态下 check() 的语义仍成立,但
|
||
* 页面要的是"定值闭锁"这个更高优先级的原因(契约 §6.3 表序)
|
||
*/
|
||
int ui_data_param_drain(const ui_data_param_ops_t *a_ops) {
|
||
const ui_data_param_req_t *p_req;
|
||
uint64_t bad;
|
||
int settled;
|
||
|
||
if ((a_ops == NULL) || (a_ops->locked == NULL) ||
|
||
(a_ops->check == NULL) || (a_ops->apply == NULL)) {
|
||
return -1;
|
||
}
|
||
if (s_pq_cnt == 0u) {
|
||
return 0;
|
||
}
|
||
|
||
p_req = &s_pq[s_pq_head];
|
||
bad = 0u;
|
||
|
||
if (a_ops->locked() != 0u) {
|
||
settled = UI_DATA_PARAM_ST_ERR_LOCKED;
|
||
} else {
|
||
settled = UI_DATA_PARAM_ST_OK;
|
||
bad = ui_data_param_scan(a_ops, p_req);
|
||
if (bad != 0u) {
|
||
settled = UI_DATA_PARAM_ST_ERR_RANGE; /* 预检挡下:一字未写 */
|
||
} else {
|
||
if (ui_data_param_write(a_ops, p_req, &bad) != 0) {
|
||
settled = UI_DATA_PARAM_ST_ERR_RANGE; /* 执行侧拒写:见函数说明 */
|
||
}
|
||
/* 两关都过 → settled 保持 OK */
|
||
}
|
||
}
|
||
|
||
s_pq_err_mask = bad;
|
||
s_pq_head = (uint8_t)(((uint32_t)s_pq_head + 1u) % UI_DATA_PARAM_QD);
|
||
s_pq_cnt -= 1u;
|
||
s_pq_state = (s_pq_cnt != 0u) ? UI_DATA_PARAM_ST_WAIT : settled;
|
||
return 1;
|
||
}
|