Files
STM32F4-Base/App/task/iec104_serv.c
T
wandering1 90db528032 W1 功能主线完成:内存台账三件套(W1-0) + IEC104 上行(B4) + protectTask 板端接线(B5)
W1-0 台账三件套:scripts/ram_baseline.py(四模式)+ ram_baseline.tsv 趋势 +
docs/内存管理/RAM预算台账.md + pre-commit mem-budget 钩子(RW_IRAM1/RW_CCM >92%
即 FAIL + Top-10 附表);审查报告 §5.5 三条闭环。

B4 IEC 60870-5-104 上行(被控站,TCP 2404):App/protocol/iec104/ 三模块
(asdu 编解码 / pts 点表+数据源 vtable / link APCI+FSM,纯逻辑零 I/O)+
App/task/iec104_serv.c(iec104Task,NET_READY 屏障 100ms 分片喂位)。
82 断言 L0.5 全绿 + iec104-unit-test 钩子(第 31 钩子);
设计说明 docs/子系统设计/IEC104上行设计说明.md 全量回填。

B5 protectTask(L3 板端接线):App/task/protect_task.c——20ms 判据节拍
prot_elm_run + L5-4 采样故障闭锁(adc_health 电平位→元件复位)+ L5-1 定值
校验和巡检;新增 prot_elm_request_reset() 收口跨上下文复归(104 YK11 落请求、
eval 头部消费);定值/元件初始化移入 app_main_init(内核前)。TC-PE-010
(prot_elm 28 用例)。

门禁与内存:TASK_WD_PROTECT=8 入 required_mask(产品 0x77→0x1F7、HW 层
0x1F5,看门狗单测 39 断言同步);FreeRTOS 堆 48→56KB(.ioc L1 参数 +
FreeRTOSConfig.h 同步,RAM预算台账 §6 登记配额 48→56KB)。
ZI 144,008→152,204(+8,196 B = 堆 +8KB,B5 扩容);Code=204,348。
stack_report:protectTask 512 words 静态链 872 B/余 57%、iec104Task
640 words 1456 B/余 43% 均 PASS;RW_IRAM1 73.1% / RW_CCM 87.9%(<92%)。

验证:@build 0 错 0 警;ci_local 全绿(31 钩子 + 13 主机侧单测);
doc-links 135 文档 PASS。登记簿 B4/B5 销项、W1 波次完成;保护规划/看门狗
说明/AGENTS(9 任务)/RAM 三文档全部同步。

遗留(验收窗口,非代码空洞):台架第三方 104 主站往返(送检窗口)、
标准源注入动作判据、@test PROT 锚点 + protect 栈水位 + 新堆 min_ever 复测。
2026-10-12 12:36:23 +08:00

296 lines
9.9 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.
/*
* 模块名称:IEC 60870-5-104 被控站服务(板端胶水任务)
* 模块功能:2404 监听 + 单连接 socket 收发 + 把字节流喂给 iec104_link 纯逻辑
* 层;对时经 sys_clock_set_unix 落墙钟;数据源走 iec104_pts 默认源
* (phasor / prot_elm / prot_set)。协议语义全部在 App/protocol/iec104,
* 本文件只做 I/O 编排(A-4 约束 1)。
* 线程约定:iec104Task 独占(NET_READY 屏障后循环,100ms 节拍 + 看门狗心跳)。
* 创建日期:2026-10-12(B4)
*/
#include "iec104_serv.h"
#define DBG_LINE_BUF_MAX 128U /* 深链模块日志缓冲收紧(time_sync 先例) */
#define DBG_TAG "[I104]"
#include "iec104_link.h"
#include "iec104_pts.h"
#include "net_socket.h"
#include "sys_clock.h"
#include "sys_events.h"
#include "task_watchdog.h"
#include "dbg_log.h"
#include "cmsis_os2.h"
#include <string.h>
/* 服务周期:COV 延迟 ≤100ms(远小于 SOE 5000ms 验收线);心跳间隔同值 */
#define IEC104_POLL_MS 100u
/* 接收搬运缓冲(静态,不入栈;link 层内部另有 512B 重组缓冲) */
#define IEC104_RX_SZ 128u
/* 兜底节拍:门控判"无数据"时每 32 轮无条件收一次(ui_net_sink RM 先例,
* 防 RECV_OK 中断丢失把数据永久藏住);须为 2 的幂 */
#define IEC104_LIVENESS_DIV 32u
/* 连续发送失败阈值:同 ui_net_sink E-04(短写=对端收坏帧) */
#define IEC104_SEND_FAIL_MAX 3u
/* ---------------- 服务状态(iec104Task 独占) ---------------- */
static iec104_link_t s_link;
static int s_listen = -1;
static int s_client = -1; /* 单连接模型:MCU 上即 s_listen */
static uint32_t s_gen; /* 网络代次(重建自愈,ui_net_sink 先例) */
static uint8_t s_rx[IEC104_RX_SZ];
static uint8_t s_liveness_ctr;
static uint8_t s_send_fail_run;
static uint8_t s_conn_dead; /* 发送连败 → 本轮 link 调用返回后拆链 */
/* 前向声明(cfg 初始化引用) */
static void s_send_apdu(void *ctx, const uint8_t *apdu, uint16_t len);
static int s_clock_sync(void *ctx, uint64_t epoch_ms);
/* V0.1 公共地址固定 1(点表口径;可配置项挂二期) */
static const iec104_link_cfg_t s_cfg = { 1u, s_send_apdu, s_clock_sync };
/*
* 函数功能:link 层发送回调——经 net_send 串行 RPC 发出一个 APDU
* 入口参数:ctx 未用;apdu/len = 完整 APCI 帧
* 返回值:无
* 限定条件:s_client >= 0(在 link 调用栈内执行,即本任务上下文)
* 函数说明:短写/失败累计(E-04 先例);连续 3 次 → 置 s_conn_dead,
* 由 s_service_link 在 link 调用返回后统一拆链(严禁在回调里拆)
*/
static void s_send_apdu(void *ctx, const uint8_t *apdu, uint16_t len) {
int ret;
(void)ctx;
if (s_client < 0) {
return;
}
ret = net_send(s_client, apdu, (int)len, 0);
if (ret == (int)len) {
s_send_fail_run = 0u;
return;
}
if (s_send_fail_run < 255u) {
s_send_fail_run++;
}
DBG_ERROR("i104 send short: ret=%d/%u run=%u", ret, (unsigned)len,
(unsigned)s_send_fail_run);
if (s_send_fail_run >= IEC104_SEND_FAIL_MAX) {
s_conn_dead = 1u;
}
}
/*
* 函数功能:link 层对时回调——epoch 毫秒落系统墙钟(含 SD2506 RTC 写回)
* 入口参数:ctx 未用;epoch_ms = C_CS 解析出的 UTC 时刻
* 返回值:0 成功 / -1 失败(link 层回 actcon+PN)
* 函数说明:uint32 秒域上限 2106-02-07,越界显式拒绝(CP56 域到 2127)
*/
static int s_clock_sync(void *ctx, uint64_t epoch_ms) {
uint64_t secs = epoch_ms / 1000ULL;
(void)ctx;
if (secs > 0xFFFFFFFFULL) {
return -1;
}
return sys_clock_set_unix((uint32_t)secs);
}
/*
* 函数功能:创建并监听 2404(socket+bind+listen,抄 ui_net_sink bind_listen)
* 返回值:0 成功(s_listen 写回)/ -1 失败
* 限定条件:net 子系统可用(NET_READY 屏障后)
*/
static int s_bind_listen(void) {
int ls;
struct net_sockaddr_in addr;
ls = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (ls < 0) {
return -1;
}
(void)memset(&addr, 0, sizeof(addr));
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons(IEC104_SERV_PORT);
addr.sin_addr.s_addr = NET_INADDR_ANY;
if (net_bind(ls, (struct net_sockaddr *)&addr, (int)sizeof(addr)) < 0) {
net_close(ls);
return -1;
}
if (net_listen(ls) < 0) {
net_close(ls);
return -1;
}
s_listen = ls;
return 0;
}
/*
* 函数功能:主动断开当前 104 连接并重建监听(协议故障/t1 超时/发送连败)
* 入口参数:why = 日志原因串(ASCII)
* 返回值:无
* 函数说明:与 EOF 路径不同——协议违例时主站不会自行断开,必须本端 close
* 促其重连;单连接模型下 client 即 listen,close 后统一重建
* (time_sync 会话收尾先例,非芯片侧 auto_relisten 风暴路径)
*/
static void s_drop_conn(const char *why) {
DBG_ERROR("i104 drop: %s", why);
if (s_client >= 0) {
net_close(s_client);
}
s_client = -1;
s_listen = -1;
iec104_link_close(&s_link);
s_send_fail_run = 0u;
s_conn_dead = 0u;
s_liveness_ctr = 0u;
if (s_bind_listen() < 0) {
s_listen = -1; /* 主循环下轮重试 */
}
}
/*
* 函数功能:link 层节拍服务 + 故障检测(poll 后查 IDLE 跳变/发送连败)
* 入口参数:now = 注入时基(tick ms)
* 返回值:无
*/
static void s_service_link(uint32_t now) {
iec104_link_poll(&s_link, now);
if (s_conn_dead != 0u) {
s_drop_conn("send fail");
return;
}
if (iec104_link_state(&s_link) == IEC104_ST_IDLE) {
s_drop_conn("protocol fault");
}
}
/*
* 函数功能:单次服务循环(代次自愈 → accept → 门控收 → 喂 link → 节拍)
* 返回值:无
* 限定条件:NET_READY 已置位;s_cfg/s_link 已 init
* 函数说明:门控(net_can_accept/net_can_read 纯 RAM 读)+ 兜底节拍照
* ui_net_sink I-1/I-2;EOF 时 client==listen 严禁 close(net 层
* 已 auto_relisten,close 会连监听一起毁)
*/
static void s_serv_poll(void) {
uint32_t now = osKernelGetTickCount();
int n;
/* 网络重建(代次变化):旧监听/客户端随芯片复位失效 → 重建 */
if (net_get_generation() != s_gen) {
if ((s_client >= 0) && (s_client != s_listen)) {
net_close(s_client);
}
if (s_listen >= 0) {
net_close(s_listen);
s_listen = -1;
}
s_client = -1;
iec104_link_close(&s_link);
s_send_fail_run = 0u;
s_conn_dead = 0u;
s_liveness_ctr = 0u;
s_gen = net_get_generation();
(void)s_bind_listen();
return;
}
if (s_listen < 0) {
(void)s_bind_listen(); /* 启动失败/被毁:重试 */
return;
}
if (s_client < 0) {
int c;
if (net_can_accept(s_listen) == 0) {
if (net_get_sock(s_listen) == NULL) {
s_listen = -1; /* 控制块消失:交回上方重建 */
}
return;
}
c = net_accept(s_listen, NULL, NULL);
if (c >= 0) {
s_client = c;
s_send_fail_run = 0u;
s_conn_dead = 0u;
s_liveness_ctr = 0u;
iec104_link_open(&s_link, now);
DBG_INFO("i104: accepted fd=%d", c);
}
return;
}
/* 门控 + 兜底:无数据多数轮直接走 link 节拍;每 32 轮无条件收一次 */
if (net_can_read(s_client) == 0) {
if (((++s_liveness_ctr) & (IEC104_LIVENESS_DIV - 1u)) != 0u) {
s_service_link(now);
return;
}
} else {
s_liveness_ctr = 0u;
}
n = net_recv(s_client, s_rx, (int)sizeof(s_rx), NET_MSG_DONTWAIT);
if (n > 0) {
iec104_link_on_rx(&s_link, s_rx, (uint16_t)n, now);
} else if (n == 0) {
/* 对端关闭:net 层已 auto_relisten;client==listen 时严禁 close */
if (s_client != s_listen) {
net_close(s_client); /* 模拟器多连接后端:数据通道独立 */
}
s_client = -1;
s_liveness_ctr = 0u;
iec104_link_close(&s_link);
if ((net_get_sock(s_listen) == NULL) && (s_bind_listen() < 0)) {
s_listen = -1;
}
return;
} else if (net_get_errno() != NET_ERR_WOULDBLOCK) {
s_drop_conn("recv err");
return;
}
s_service_link(now);
}
/*
* 函数功能:iec104Task 任务体(freertos.c USER CODE 区创建)
* 入口参数:argument 未用
* 返回值:无(不返回)
* 函数说明:NET_READY 屏障(100ms 分片 + 每片喂心跳,timeSyncTask 先例)→
* 点表/链路 init + 建监听 → 100ms 服务循环(每轮喂看门狗)
*/
void iec104_serv_task(void *argument) {
(void)argument;
if (sys_evtHandle != NULL) {
for (;;) {
uint32_t r = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
osFlagsWaitAny | osFlagsNoClear, 100u);
if ((r & osFlagsError) == 0u) {
break; /* 就绪位是状态位(NoClear),不消费 */
}
task_wd_beat(TASK_WD_IEC104);
}
}
iec104_pts_init();
iec104_link_init(&s_link, &s_cfg, NULL);
s_gen = net_get_generation();
if (s_bind_listen() < 0) {
DBG_ERROR("i104: bind port %u FAIL, retry in loop",
(unsigned)IEC104_SERV_PORT);
} else {
DBG_INFO("i104: listening on %u", (unsigned)IEC104_SERV_PORT);
}
for (;;) {
task_wd_beat(TASK_WD_IEC104);
s_serv_poll();
osDelay(IEC104_POLL_MS);
}
}