Files
STM32F4-Base/App/algo/prot_set.c
T
wandering1 1fe163ba7b feat(protect): B7 定值持久化 + 全局软压板 + L5-1 竞态收口(W1 代码腿完成)
- prot_set:新增 PS_GLOBAL_EN(枚举尾追加);值区双缓冲 + 单字交换,
  prot_set_set_sealed 原子写+封存;selfcheck 改 seqlock 式复核
  (提交撞见 = 本轮跳过)→ 收口 B5 登记的 µs 级误锁窗口
- 持久化:新增 App/cfg/prot_set_store.c/h,/prot_set.bin blob
  (magic+版本+计数+值区+sum32 正/反码对,144 B)走 cfg_store_write_atomic;
  uvprojx 注册 + 模拟器豁免点名(check_registration 同步)
- 写路径:C_SE/YK12 执行段置脏 → iec104Task 投递 SYS_EVT_PROT_SET_SAVE
  (事件位 bit7)→ defaultTask(配置引擎属主)落盘,写失败脏回置 = 天然
  重试;装载 = STORAGE_READY 屏障前整表原子 parse(损坏保出厂默认 +
  自检留痕 prot set corrupt;文件缺失 = 首启静默)
- YK12 转真:两段式(选择段校验 DPC / 执行段 set_sealed),protectTask
  下一拍读退出态 → 复位元件 + 停判据(global plate OUT/IN 边沿日志);
  本地定值项 comm_addr=0 显式不可寻址(防 IOA=0 误命中首个本地项)
- 单测:prot_set 63 断言(+33:软压板/原子写/脏握手/blob 往返/损坏拒绝/
  伪造自洽越界对抗样本)、iec104 94 断言(+7:YK12 两段转真/置脏/本地项
  C_RD EVI)
- 文档:IEC104 设计说明 §3.3/§3.4/§7、保护规划 §3.5 L5-1/§7 P4/变更记录、
  App/algo 与 App/cfg README、W1 任务卡 B7→🟦(台架腿 🔴 待窗口,随 B6 同窗)
- 验证:@build 0 错 0 警(Code=205500,ZI +280 = 双缓冲+blob 缓冲);
  ci_local 全绿(L0 27 钩子 + L0.5 13 套件 826 断言);ram_baseline 台账
  受管区与内存快照已同步;19 个改动文件无 BOM / LF / 零乱码
2026-10-12 15:01:49 +08:00

438 lines
16 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.
/*
* 模块名称:保护定值模型(P1,L5-1 载体)——实现
* 模块功能:见 prot_set.h。纯逻辑,零平台依赖(仅 stdint.h + 自身头)。
*
* ⚠ 默认值/范围为拟定值(参考 EEPROM 出厂默认表不在本仓库,规划 §10.3 待补);
* 通讯地址逐项对照参考 Comm/Pub/Const_CommSetPara.c g_tCommSetParaTab。
*
* 创建日期:2026-10-11
*/
#include "prot_set.h"
#include <string.h> /* memcpy(set_sealed 影子区拷贝,B7) */
/* 元数据表:顺序必须与 prot_set_id_t 严格一致(逐行带地址注释,同 phasor
* k_inst 的朴素行式风格) */
const prot_set_meta_t g_prot_set_meta[PS_ID_COUNT] = {
/* addr ctl default min max 说明 */
{ 0x8856, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* OC1 投退 */
{ 0x8857, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* OC1 跳闸投退 */
{ 0x8858, 0u, PROT_SET_Q10(10.0), 0, PROT_SET_Q10(100.0) }, /* OC1 电流 A */
{ 0x8859, 0u, PROT_SET_Q10(0.0), 0, PROT_SET_Q10(60000) }, /* OC1 延时 ms */
{ 0x885A, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) },
{ 0x885B, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) },
{ 0x885C, 0u, PROT_SET_Q10(5.0), 0, PROT_SET_Q10(100.0) },
{ 0x885D, 0u, PROT_SET_Q10(500.0), 0, PROT_SET_Q10(60000) },
{ 0x885E, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) },
{ 0x885F, 1u, PROT_SET_Q10(0), 0, PROT_SET_Q10(1) }, /* OC3 默认告警 */
{ 0x8860, 0u, PROT_SET_Q10(2.0), 0, PROT_SET_Q10(100.0) },
{ 0x8861, 0u, PROT_SET_Q10(3000.0), 0, PROT_SET_Q10(60000) },
{ 0x8871, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* 过负荷投退 */
{ 0x8872, 1u, PROT_SET_Q10(0), 0, PROT_SET_Q10(1) }, /* 默认告警 */
{ 0x8873, 0u, PROT_SET_Q10(1.0), 0, PROT_SET_Q10(100.0) },
{ 0x8874, 0u, PROT_SET_Q10(10000.0),0, PROT_SET_Q10(60000) },
{ 0x0000, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* OV 投退(本地) */
{ 0x8029, 0u, PROT_SET_Q10(120.0), 0, PROT_SET_Q10(200.0) }, /* 过压 V */
{ 0x802A, 0u, PROT_SET_Q10(10000.0),0, PROT_SET_Q10(60000) },
{ 0x0000, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* UV 投退(本地) */
{ 0x8027, 0u, PROT_SET_Q10(20.0), 0, PROT_SET_Q10(200.0) }, /* 欠压 V */
{ 0x8028, 0u, PROT_SET_Q10(10000.0),0, PROT_SET_Q10(60000) },
{ 0x888E, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* 3U0 投退 */
{ 0x888F, 0u, PROT_SET_Q10(15.0), 0, PROT_SET_Q10(200.0) }, /* 3U0 V */
{ 0x8890, 0u, PROT_SET_Q10(5000.0), 0, PROT_SET_Q10(60000) },
{ 0x0000, 0u, PROT_SET_Q10(0.95), PROT_SET_Q10(0.5), PROT_SET_Q10(1.0) }, /* 过量返回系数 */
{ 0x0000, 0u, PROT_SET_Q10(1.05), PROT_SET_Q10(1.0), PROT_SET_Q10(1.5) }, /* 欠量返回系数 */
{ 0x8820, 0u, PROT_SET_Q10(10000.0),PROT_SET_Q10(100.0), PROT_SET_Q10(500000.0) },
{ 0x8821, 0u, PROT_SET_Q10(100.0), PROT_SET_Q10(1.0), PROT_SET_Q10(200.0) },
{ 0x8825, 0u, PROT_SET_Q10(100.0), PROT_SET_Q10(1.0), PROT_SET_Q10(10000.0) },
{ 0x8826, 0u, PROT_SET_Q10(5.0), PROT_SET_Q10(1.0), PROT_SET_Q10(10.0) },
{ 0x0000, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* 全局软压板投退(YK12,B7) */
};
static uint32_t s_val[2][PS_ID_COUNT]; /* 双缓冲值区(uint32 承载 int32,校验和按字覆盖)
* B7:写者改影子区后单字交换 s_act,
* 读侧(protectTask selfcheck/get)永远看到一致快照 */
static volatile uint8_t s_act; /* 活跃区下标(0/1);交换 = 单字存储 */
static uint32_t s_sum; /* 封存的正码 */
static uint32_t s_sumn; /* 封存的反码 */
static uint32_t s_bad_mask; /* load_raw/parse 越界位图 */
static uint8_t s_locked; /* 定值非法 → 闭锁(L5-1) */
static uint8_t s_dirty; /* 未落盘定值改动(B7 持久化脏标志) */
/*
* 函数功能:sum32 覆盖指定值区(参考 Fun_CalSum32 同款:32 位字累加自然回绕)
* 入口参数:vals - PS_ID_COUNT 个字的值区
* 返 回 值:校验和正码
*/
static uint32_t prot_sum_buf(const uint32_t *vals) {
uint32_t sum = 0u;
uint32_t i;
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
sum += vals[i];
}
return sum;
}
/*
* 函数功能:把元数据表默认值刷进 RAM 值区
* 入口参数:无
* 返 回 值:无
*/
static void prot_load_defaults(void) {
uint32_t i;
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
s_val[s_act][i] = (uint32_t)g_prot_set_meta[i].def;
}
}
/*
* 函数功能:装载出厂默认值并封存校验和(幂等)
* 入口参数:无
* 返 回 值:无
*/
void prot_set_init(void) {
prot_load_defaults();
s_bad_mask = 0u;
s_locked = 0u;
s_dirty = 0u;
prot_set_seal();
}
/*
* 函数功能:取最近一次 load_raw 的越界位图
* 入口参数:无
* 返 回 值:bit i = 第 i 项越界(load_raw 失败后有效)
*/
uint32_t prot_set_bad_mask(void) {
return s_bad_mask;
}
/*
* 函数功能:整表装载外部值(P4 起由 cfg_file 定值段调用),逐项范围校验
* 入口参数:vals - PS_ID_COUNT 个 Q10 值
* 返 回 值:0 全部合法(已装载并封存)/ -1 存在越界项或空指针(整表不装载)
* 函数说明:all-or-nothing——宁可全旧不可半新(定值一致性优先);
* 越界项经 prot_set_bad_mask() 指认。
*/
int prot_set_load_raw(const int32_t *vals) {
uint32_t mask = 0u;
uint32_t cur_act;
uint32_t *shadow;
uint32_t i;
if (vals == 0) {
return -1;
}
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
if ((vals[i] < g_prot_set_meta[i].min) || (vals[i] > g_prot_set_meta[i].max)) {
mask |= (1uL << i);
}
}
s_bad_mask = mask;
if (mask != 0u) {
return -1;
}
/* 原子装载(B7):写影子区 → 封存 → 单字交换;读侧无中间态 */
cur_act = s_act;
shadow = s_val[cur_act ^ 1u];
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
shadow[i] = (uint32_t)vals[i];
}
s_sum = prot_sum_buf(shadow);
s_sumn = ~s_sum;
s_locked = 0u;
s_act = (uint8_t)(cur_act ^ 1u);
return 0;
}
/*
* 函数功能:单项写入(范围校验)
* 入口参数:id - 定值项编号;q10 - Q10 定点值
* 返 回 值:0 / -1 越界或非法编号拒写(原值不动)
* 函数说明:**不自动重封校验和**——批量改完必须显式 prot_set_seal(),
* 否则 selfcheck 判"定值被非法改写"(参考系统改定值走重写整表)。
*/
int prot_set_set(prot_set_id_t id, int32_t q10) {
if (id >= PS_ID_COUNT) {
return -1;
}
if ((q10 < g_prot_set_meta[id].min) || (q10 > g_prot_set_meta[id].max)) {
return -1;
}
s_val[s_act][(uint32_t)id] = (uint32_t)q10;
return 0;
}
/*
* 函数功能:单项**原子**写入+封存(B7,收口 B5 竞态窗口)
* 入口参数:id - 定值项编号;q10 - Q10 定点值
* 返 回 值:0 / -1 越界或非法编号拒写(原值不动)
* 函数说明:影子区拷贝 → 改影子 → 按影子重封校验和 → 单字交换活跃区。
* selfcheck 任何时刻看到的都是"值区与封存一致"的完整快照
* (区别于 prot_set_set+seal 批处理路径;两路径勿在一次批处理中交错)。
* 置脏标志 → 104 任务通知 defaultTask(配置引擎属主)落盘。
*/
int prot_set_set_sealed(prot_set_id_t id, int32_t q10) {
uint32_t cur_act;
uint32_t *shadow;
if (id >= PS_ID_COUNT) {
return -1;
}
if ((q10 < g_prot_set_meta[id].min) || (q10 > g_prot_set_meta[id].max)) {
return -1;
}
cur_act = s_act;
shadow = s_val[cur_act ^ 1u];
(void)memcpy(shadow, s_val[cur_act], sizeof(s_val[0]));
shadow[(uint32_t)id] = (uint32_t)q10;
s_sum = prot_sum_buf(shadow);
s_sumn = ~s_sum;
s_locked = 0u;
s_act = (uint8_t)(cur_act ^ 1u); /* 单字交换:读侧原子切换 */
s_dirty = 1u;
return 0;
}
/*
* 函数功能:脏标志三件套(B7 持久化握手)
* 入口参数:无
* 返 回 值:peek/take = 1 存在未落盘改动 / 0 干净;mark 无返回
* 函数说明:peek 非消费(iec104Task 每轮通知,直到落盘成功);
* take 消费(defaultTask 落盘前);mark 写盘失败回置(等下轮重试)。
* 单字节标志,多读者安全;写者只有 set_sealed(置)与属主任务(清/置)。
*/
uint8_t prot_set_dirty_peek(void) {
return s_dirty;
}
uint8_t prot_set_dirty_take(void) {
uint8_t d = s_dirty;
s_dirty = 0u;
return d;
}
void prot_set_dirty_mark(void) {
s_dirty = 1u;
}
/*
* 函数功能:读定值 Q10 原值
* 入口参数:id - 定值项编号
* 返 回 值:Q10 值;非法编号返回 0
*/
int32_t prot_set_get(prot_set_id_t id) {
if (id >= PS_ID_COUNT) {
return 0;
}
return (int32_t)s_val[s_act][(uint32_t)id];
}
/*
* 函数功能:读定值工程量浮点值
* 入口参数:id - 定值项编号
* 返 回 值:Q10 → float;非法编号返回 0.0
*/
float prot_set_get_f(prot_set_id_t id) {
return PROT_SET_FROM_Q10(prot_set_get(id));
}
/*
* 函数功能:读投退/枚举项
* 入口参数:id - 定值项编号
* 返 回 值:!=0 视为投入(1/0)
*/
uint8_t prot_set_get_ctl(prot_set_id_t id) {
return (prot_set_get(id) != 0) ? 1u : 0u;
}
/*
* 函数功能:实时计算校验和(不读封存值)
* 入口参数:无
* 返 回 值:sum32 正码
*/
uint32_t prot_set_checksum(void) {
return prot_sum_buf(s_val[s_act]);
}
/*
* 函数功能:封存当前值区的正/反码对(合法改定值闭环的最后一步)
* 入口参数:无
* 返 回 值:无
* 函数说明:封存同时解除闭锁——selfcheck 失配后的唯一合法解锁路径。
* ⚠ 批处理路径(prot_set_set 多次 + 本函数收尾)在 set 与 seal
* 之间存在"已改值未封存"窗口,selfcheck 会如实判失配(防篡改语义);
* 板端单项写一律走 prot_set_set_sealed(原子,无此窗口)。
*/
void prot_set_seal(void) {
s_sum = prot_sum_buf(s_val[s_act]);
s_sumn = ~s_sum;
s_locked = 0u;
}
/*
* 函数功能:定值自检(L5-1)——重算 sum32 并与封存值比对
* 入口参数:无
* 返 回 值:0 通过 / -1 失配(已置闭锁)
* 函数说明:失配 → s_locked 置位并保持,直到下一次 seal;
* 元件层每拍读 prot_set_locked() 决定整体复位(联动闭锁)。
* B7 双缓冲 + seqlock 式复核:先读封存和、再读值区、再读封存和,
* 两次不同 = 检查期间发生了原子提交 → 本轮不作数(下拍再查),
* 杜绝"提交瞬间被巡检撞见"的误锁(收口 B5 竞态窗口)。
*/
int prot_set_selfcheck(void) {
uint32_t sum_a = s_sum;
uint32_t calc = prot_sum_buf(s_val[s_act]);
uint32_t sum_b = s_sum;
if (sum_a != sum_b) {
/* 检查期间有 set_sealed/load_raw 提交:快照不一致,本轮跳过 */
return (s_locked != 0u) ? -1 : 0;
}
if ((calc != sum_a) || (sum_a != ~s_sumn)) {
s_locked = 1u; /* 值区与封存不符:定值被非法改写 → 闭锁 */
}
return (s_locked != 0u) ? -1 : 0;
}
/*
* 函数功能:读闭锁状态
* 入口参数:无
* 返 回 值:1 = 定值非法(保护应整体闭锁)/ 0 = 正常
*/
uint8_t prot_set_locked(void) {
return s_locked;
}
/* ==================== 持久化 blob(B7,纯逻辑) ========================= */
/* 小端编解码(blob 跨端一致;主机单测与 MCU 同一份字节口径) */
/*
* 函数功能:16 位值按小端写入缓冲
* 入口参数:p - 写位置(≥2 字节余量);v - 值
* 返 回 值:无
*/
static void s_put16(uint8_t *p, uint16_t v) {
p[0] = (uint8_t)(v & 0xFFu);
p[1] = (uint8_t)(v >> 8);
}
/*
* 函数功能:32 位值按小端写入缓冲
* 入口参数:p - 写位置(≥4 字节余量);v - 值
* 返 回 值:无
*/
static void s_put32(uint8_t *p, uint32_t v) {
p[0] = (uint8_t)(v & 0xFFu);
p[1] = (uint8_t)((v >> 8) & 0xFFu);
p[2] = (uint8_t)((v >> 16) & 0xFFu);
p[3] = (uint8_t)(v >> 24);
}
/*
* 函数功能:从小端缓冲读 16 位值
* 入口参数:p - 读位置
* 返 回 值:值 uint16_t
*/
static uint16_t s_get16(const uint8_t *p) {
return (uint16_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8));
}
/*
* 函数功能:从小端缓冲读 32 位值
* 入口参数:p - 读位置
* 返 回 值:值 uint32_t
*/
static uint32_t s_get32(const uint8_t *p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
/*
* 函数功能:把当前值区序列化为 blob(校验和按值区现算,不依赖封存值)
* 入口参数:p_buf - 输出缓冲;cap - 容量;p_len - 实际长度输出
* 返 回 值:0 / -1 参数非法或容量不足
*/
int prot_set_store_serialize(uint8_t *p_buf, uint32_t cap, uint32_t *p_len) {
const uint32_t *vals = s_val[s_act];
uint32_t sum = 0u;
uint32_t i;
if ((p_buf == 0) || (p_len == 0) || (cap < PROT_SET_BLOB_SZ)) {
return -1;
}
s_put32(p_buf, PROT_SET_BLOB_MAGIC);
s_put16(p_buf + 4u, (uint16_t)PROT_SET_BLOB_VER);
s_put16(p_buf + 6u, (uint16_t)PS_ID_COUNT);
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
s_put32(p_buf + PROT_SET_BLOB_HDR_SZ + 4u * i, vals[i]);
sum += vals[i];
}
s_put32(p_buf + PROT_SET_BLOB_SZ - 8u, sum);
s_put32(p_buf + PROT_SET_BLOB_SZ - 4u, ~sum);
*p_len = PROT_SET_BLOB_SZ;
return 0;
}
/*
* 函数功能:解析 blob 并**整表原子**装载(all-or-nothing)
* 入口参数:p_buf/len - blob 字节流
* 返 回 值:0 已装载并封存 / -1 拒绝(magic/版本/长度/校验对不符或任一项
* 越界 → 活跃值区一字不动;越界项经 prot_set_bad_mask() 指认)
* 函数说明:解码/校验全在影子区进行,只有全部通过才交换活跃区——
* 损坏 blob 不可能污染在用的定值(掉电/写坏/版本换代同一路径)。
*/
int prot_set_store_parse(const uint8_t *p_buf, uint32_t len) {
uint32_t cur_act;
uint32_t *shadow;
uint32_t sum = 0u;
uint32_t mask = 0u;
uint32_t i;
if ((p_buf == 0) || (len != PROT_SET_BLOB_SZ)) {
return -1;
}
if (s_get32(p_buf) != PROT_SET_BLOB_MAGIC) {
return -1;
}
if (s_get16(p_buf + 4u) != (uint16_t)PROT_SET_BLOB_VER) {
return -1;
}
if (s_get16(p_buf + 6u) != (uint16_t)PS_ID_COUNT) {
return -1;
}
cur_act = s_act;
shadow = s_val[cur_act ^ 1u];
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
uint32_t v = s_get32(p_buf + PROT_SET_BLOB_HDR_SZ + 4u * i);
shadow[i] = v;
sum += v;
}
if (sum != s_get32(p_buf + PROT_SET_BLOB_SZ - 8u)) {
return -1; /* 校验和正码不符(影子区未交换前不可见,无害) */
}
if (~sum != s_get32(p_buf + PROT_SET_BLOB_SZ - 4u)) {
return -1; /* 反码对不符 */
}
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
if (((int32_t)shadow[i] < g_prot_set_meta[i].min)
|| ((int32_t)shadow[i] > g_prot_set_meta[i].max)) {
mask |= (1uL << i);
}
}
s_bad_mask = mask;
if (mask != 0u) {
return -1; /* 校验对一致但含越界项:同样整表拒绝 */
}
s_sum = sum;
s_sumn = ~sum;
s_locked = 0u;
s_act = (uint8_t)(cur_act ^ 1u);
return 0;
}