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 / 零乱码
This commit is contained in:
+228
-19
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
#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] = {
|
||||
@@ -44,25 +46,30 @@ const prot_set_meta_t g_prot_set_meta[PS_ID_COUNT] = {
|
||||
{ 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[PS_ID_COUNT]; /* RAM 值区(uint32 承载 int32,校验和按字覆盖) */
|
||||
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 越界位图 */
|
||||
static uint32_t s_bad_mask; /* load_raw/parse 越界位图 */
|
||||
static uint8_t s_locked; /* 定值非法 → 闭锁(L5-1) */
|
||||
static uint8_t s_dirty; /* 未落盘定值改动(B7 持久化脏标志) */
|
||||
|
||||
/*
|
||||
* 函数功能:sum32 覆盖 RAM 值区(参考 Fun_CalSum32 同款:32 位字累加自然回绕)
|
||||
* 入口参数:无
|
||||
* 函数功能:sum32 覆盖指定值区(参考 Fun_CalSum32 同款:32 位字累加自然回绕)
|
||||
* 入口参数:vals - PS_ID_COUNT 个字的值区
|
||||
* 返 回 值:校验和正码
|
||||
*/
|
||||
static uint32_t prot_sum32(void) {
|
||||
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 += s_val[i];
|
||||
sum += vals[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
@@ -76,7 +83,7 @@ static void prot_load_defaults(void) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
s_val[i] = (uint32_t)g_prot_set_meta[i].def;
|
||||
s_val[s_act][i] = (uint32_t)g_prot_set_meta[i].def;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +96,7 @@ void prot_set_init(void) {
|
||||
prot_load_defaults();
|
||||
s_bad_mask = 0u;
|
||||
s_locked = 0u;
|
||||
s_dirty = 0u;
|
||||
prot_set_seal();
|
||||
}
|
||||
|
||||
@@ -110,6 +118,8 @@ uint32_t prot_set_bad_mask(void) {
|
||||
*/
|
||||
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) {
|
||||
@@ -124,10 +134,16 @@ int prot_set_load_raw(const int32_t *vals) {
|
||||
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++) {
|
||||
s_val[i] = (uint32_t)vals[i];
|
||||
shadow[i] = (uint32_t)vals[i];
|
||||
}
|
||||
prot_set_seal();
|
||||
s_sum = prot_sum_buf(shadow);
|
||||
s_sumn = ~s_sum;
|
||||
s_locked = 0u;
|
||||
s_act = (uint8_t)(cur_act ^ 1u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -145,10 +161,64 @@ int prot_set_set(prot_set_id_t id, int32_t q10) {
|
||||
if ((q10 < g_prot_set_meta[id].min) || (q10 > g_prot_set_meta[id].max)) {
|
||||
return -1;
|
||||
}
|
||||
s_val[(uint32_t)id] = (uint32_t)q10;
|
||||
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 - 定值项编号
|
||||
@@ -158,7 +228,7 @@ int32_t prot_set_get(prot_set_id_t id) {
|
||||
if (id >= PS_ID_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
return (int32_t)s_val[(uint32_t)id];
|
||||
return (int32_t)s_val[s_act][(uint32_t)id];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -185,7 +255,7 @@ uint8_t prot_set_get_ctl(prot_set_id_t id) {
|
||||
* 返 回 值:sum32 正码
|
||||
*/
|
||||
uint32_t prot_set_checksum(void) {
|
||||
return prot_sum32();
|
||||
return prot_sum_buf(s_val[s_act]);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -193,9 +263,12 @@ uint32_t prot_set_checksum(void) {
|
||||
* 入口参数:无
|
||||
* 返 回 值:无
|
||||
* 函数说明:封存同时解除闭锁——selfcheck 失配后的唯一合法解锁路径。
|
||||
* ⚠ 批处理路径(prot_set_set 多次 + 本函数收尾)在 set 与 seal
|
||||
* 之间存在"已改值未封存"窗口,selfcheck 会如实判失配(防篡改语义);
|
||||
* 板端单项写一律走 prot_set_set_sealed(原子,无此窗口)。
|
||||
*/
|
||||
void prot_set_seal(void) {
|
||||
s_sum = prot_sum32();
|
||||
s_sum = prot_sum_buf(s_val[s_act]);
|
||||
s_sumn = ~s_sum;
|
||||
s_locked = 0u;
|
||||
}
|
||||
@@ -206,16 +279,23 @@ void prot_set_seal(void) {
|
||||
* 返 回 值:0 通过 / -1 失配(已置闭锁)
|
||||
* 函数说明:失配 → s_locked 置位并保持,直到下一次 seal;
|
||||
* 元件层每拍读 prot_set_locked() 决定整体复位(联动闭锁)。
|
||||
* B7 双缓冲 + seqlock 式复核:先读封存和、再读值区、再读封存和,
|
||||
* 两次不同 = 检查期间发生了原子提交 → 本轮不作数(下拍再查),
|
||||
* 杜绝"提交瞬间被巡检撞见"的误锁(收口 B5 竞态窗口)。
|
||||
*/
|
||||
int prot_set_selfcheck(void) {
|
||||
if (prot_sum32() != s_sum) {
|
||||
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; /* 值区与封存不符:定值被非法改写 → 闭锁 */
|
||||
}
|
||||
if ((s_sum != (~s_sumn)) || (s_locked != 0u)) {
|
||||
s_locked = 1u;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
return (s_locked != 0u) ? -1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -226,3 +306,132 @@ int prot_set_selfcheck(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user