fff961d803
前置来自 #57 SNTP 调研:旧 net_sendto / net_recvfrom 由调用方直调 SPI 且无限自旋收包,既违约「netTask 是芯片唯一访问者」,又把 IWDG 预算吃在自旋里。 改造面 - 新增 Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译): bind/set_peer/sendto/recvfrom 四条执行体 + 挂起收包表 + tick; net_msg_type_t 加四型,公开 API 一律「入队 → 睡在通知上」。 - 源地址出参不搭 net_msg_t(按值拷贝,AGENTS 禁加回传字段):改住 net_udp.c 的 per-fd 槽,netTask 就地写回调用方内存,通知值只带 结果码;每条出口都清槽,泄漏 = 该 fd 永久 BUSY。 - 发送复用 net_send_locked,不写第二份分块逻辑;「会话是否已死」 收成一条判据 net_send_state_down()(TCP 看 ESTABLISHED、UDP 看 UDP_OPEN),原先 4 处各写一遍。 - 顺带修掉真缺陷(新条 TRAP-NET-22):旧 sendto 把 DesIP 写成具体 IP,芯片静默切 UDP Client,而旧 recvfrom 无条件剥 8 字节 → SNTP 这类 48 B 应答丢载荷前 8 字节并交出垃圾源地址。加 udp_mode(塞进 原有 padding,sizeof 不变)、bind 恒 SERVER、显式对端 sendto 切 CLIENT、set_peer(fd, NULL) 轻量回 SERVER,仅 SERVER 且 avail >= 8 才剥头。 - 截断定案:一次调用交付一个数据报,buf 装不下的尾巴用 64 B 栈上 scratch 排空(不用 clear_recv_buf——它连后面已到达的包一起丢)。 - 取消点接齐:net_init 重建 cancel_all、net_socket 分配 fd clear、 net_close settle_close + clear、重 bind 前先结算等包者, net_process_messages 第 5 个 tick 位挂 net_udp_tick。 - 收紧两处契约(原实现无生产调用者):UDP 未 bind 一律 -5; net_recv / _nb / _to 拒 DGRAM(绕过剥头就是静默吃 8 字节); NET_ERR_NOT_IN_NETTASK(-12) 退役为历史常量并写明原因。 验证 - 主机单测 test/host/net_socket 全套 616/616 绿(改造前 470):新 分片 ns_s7_udp.c 20 组,改写 440 / 555 / 560 组;覆盖率 net_udp.c 234/236(99.2%)、net_socket.c 956/969(98.7%), scripts/test_coverage_baseline.json 随本提交登记新文件。 - 测试抓到 4 个被测码真 bug:自等守卫误用调用侧谓词、芯片长度被读 两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。 - Keil ARMCC 0 Error(s) 0 Warning(s);net_udp.o Code 1,358 / ZI 224 (8 fd × 28 B,其余模块零增长),全仓 RW_IRAM1 72.2%;内存台账 受管区按门禁提示用 --write / --update 机器回填,未手改数字。 - pre-commit 全量钩子(含 doc-links、coding-standard、mem-budget、 generated-fresh、bsp-err、registration、nested-comment)全绿。 限度 - 本轮无板子:真机回归 5 条(两端互通、set_peer(NULL) 能否恢复收 广播、截断排空后下一包完整性、挂起 recv 期间 IWDG 余量、bind 写 DesIP 与 open 先后的真芯片效果)已写进覆盖率口径「台架必验」。 - scripts/stack_report.py 退出码 1 是 10-26 起挂在账上的 iec104Task 栈债(margin 1,016 < 1,024,见 docs/代码规范/pre-commit使用说明.md: 122);本轮只让 net_bind 的 UDP 分支多一层静态帧(1,544 → 1,600), netTask margin 1,888 与 timeSyncTask 1,152 不受影响。抬栈要过 mem-budget 台账,不在本分支自行处理。 - 登记簿销项按仓库约定留给主工作树,本分支不动 docs/任务规划/**。
191 lines
12 KiB
C
191 lines
12 KiB
C
/*
|
||
* 模块名称:UDP 数据面执行器与挂起槽(netTask 串行化)
|
||
* 文件说明:UDP 三个碰芯片的动作(bind 打开、sendto、recvfrom)在 netTask 上下文
|
||
* 里的实际执行体,加上 per-fd 的"一次接收请求"槽位(载荷缓冲 + 源地址
|
||
* 出参 + 等待预算)。公开 API(net_sendto / net_recvfrom /
|
||
* net_recvfrom_to / net_udp_set_peer / net_udp_get_mode)住在
|
||
* net_socket.h,那边只负责参数校验、装消息入队、等通知值回结果。
|
||
* 为什么单独成模块:源地址(src_addr/addrlen)是**出参指针**,不能塞进按值拷入
|
||
* 队列的 net_msg_t(AGENTS 明文「勿给 net_msg_t 加回传用字段」;
|
||
* 指针过 int 桥在 64 位主机单测构建还会被截断)。所以出参走本模块的
|
||
* per-fd 槽:调用方入队前登记自己的指针,netTask 结算时把源地址写进
|
||
* 调用方内存、错误码/字节数搭任务通知值回去——与 TCP 挂起 recv 把数据
|
||
* 直接写进调用方 buf 是同一个模型(docs/网络/Net层阻塞模型架构设计.md)。
|
||
* 依赖:net_types.h(net_sock_t / NET_UDP_MODE_*)、ch395f.h(芯片事务)、
|
||
* net_socket.c(net_get_sock / net_poll / 消息分派 / 钩子调用点)
|
||
* 限定条件:除 net_udp_outparam_arm / net_udp_outparam_disarm / net_udp_clear(
|
||
* 纯 RAM,任意上下文)外,全部函数须 netTask 上下文
|
||
* 关联文档:docs/网络/BSD_Socket_API_使用指南.md §6(UDP 两种芯片模式与队列化)
|
||
* 作者:super_agent_bot
|
||
* 创建日期:2026-11-03
|
||
* 修改记录:
|
||
* 2026-11-03 super_agent_bot 创建:UDP 队列化改造(工单前置 = SNTP 客户端)
|
||
*/
|
||
#ifndef NET_UDP_H
|
||
#define NET_UDP_H
|
||
|
||
#include <stdint.h>
|
||
#include "FreeRTOS.h"
|
||
#include "task.h"
|
||
#include "net_types.h"
|
||
|
||
/*
|
||
* 函数功能:登记本次接收的源地址出参(调用方上下文,入队**前**调用)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* src_addr - 源地址出参指针(可为 NULL = 不关心来源) struct net_sockaddr *
|
||
* addrlen - 出参长度指针(可为 NULL) int *
|
||
* 出口参数:无
|
||
* 返回值:0 - 登记成功 int
|
||
* -1 - 失败(NET_ERR_BADF / NET_ERR_BUSY:本 fd 已有在途接收)
|
||
* 限定条件:任意上下文(临界区内检查并占位,与 netTask 的读改不竞态)
|
||
* 函数说明:占位成功后本 fd 的第二次接收请求会被拒(BUSY)——同一 fd 并发
|
||
* recvfrom 在数据报语义上无法区分归属,拒绝比交错投递安全。
|
||
* 调用方若入队失败必须 net_udp_outparam_disarm 归还槽位。
|
||
*/
|
||
int net_udp_outparam_arm(int sockfd, struct net_sockaddr *src_addr, int *addrlen);
|
||
|
||
/*
|
||
* 函数功能:归还源地址出参槽(调用方上下文,入队失败/取消后的清理)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* 出口参数:无
|
||
* 返回值:无(越界或空槽静默返回)
|
||
* 限定条件:任意上下文;仅在**未收到 netTask 回执**的失败路径调用
|
||
* 函数说明:netTask 结算路径自己清槽(幂等),本函数只服务调用方侧的
|
||
* "消息没投递出去"这一条出口,防槽位泄漏导致该 fd 永久 BUSY。
|
||
*/
|
||
void net_udp_outparam_disarm(int sockfd);
|
||
|
||
/*
|
||
* 函数功能:UDP 绑定/打开芯片侧 Socket(netTask 上下文执行体,恒服务器模式)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* local_port - 本地(源)端口,主机序;0 = 由本函数分配动态端口 uint16_t
|
||
* 出口参数:无
|
||
* 返回值:0 - 成功 int
|
||
* -1 - 失败(NET_ERR_BADF 描述符非法 / NET_ERR_INVAL 非 DGRAM /
|
||
* NET_ERR_BUSY 芯片 open 被拒;errno 已记)
|
||
* 限定条件:netTask 上下文;sockfd 对应控制块在用且为 DGRAM
|
||
* 函数说明:1. 序列按手册 §9.2.4:Socket 4~7 兜底重设收发缓冲 → SET_PROTO_TYPE(UDP)
|
||
* → SET_SOUR_PORT → SET_DES_IP(0xFFFFFFFF) → SET_DES_PORT(0) → OPEN_SOCKET。
|
||
* 2. open 之后写端口/IP 会被 open 复位,故目的地址必须在 open 前下发
|
||
* (docs/网络/BSD_Socket_API_函数调用关系.md §4 调用链表)。
|
||
* 3. DesIP 恒取 0xFFFFFFFF = **服务器模式**:BSD 的 bind 只表达本地地址、
|
||
* 不选对端,客户端模式由 net_udp_set_peer_exec / sendto(带目的地址) 进入。
|
||
* 4. 动态端口分配在本函数内做(原在调用方上下文的 net_bind 里写
|
||
* s_dynamic_port——芯片事务既然挪进 netTask,端口簿记一并挪,
|
||
* 避免两个上下文改同一个静态量;取号游标经 net_dynamic_port_next 共用)。
|
||
* 5. 顺带本 fd 生命周期收口:清挂起接收槽、udp_mode 置 SERVER、清对端
|
||
* 簿记、清 readable(重开不得继承上一条连接的残留电平)。
|
||
*/
|
||
int net_udp_bind_exec(int sockfd, uint16_t local_port);
|
||
|
||
/*
|
||
* 函数功能:设置芯片 DesIP/DesPort 并切换 UDP 模式(不重开 Socket,netTask 执行体)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* peer - 非 NULL = 客户端模式(对端);NULL = 服务器模式 const struct net_sockaddr_in *
|
||
* 出口参数:无
|
||
* 返回值:0 - 成功 int
|
||
* -1 - 失败(NET_ERR_BADF / NET_ERR_INVAL,errno 已记)
|
||
* 限定条件:netTask 上下文;控制块在用、type == DGRAM、芯片侧 Socket 已打开
|
||
* 函数说明:公开语义见 net_socket.h net_udp_set_peer。只写两个寄存器
|
||
* (SET_DES_IP + SET_DES_PORT,约数十微秒),不 CLOSE/OPEN,
|
||
* 故接收缓冲里已到的数据不丢。
|
||
*/
|
||
int net_udp_set_peer_exec(int sockfd, const struct net_sockaddr_in *peer);
|
||
|
||
/*
|
||
* 函数功能:UDP 发送执行体(netTask 上下文,分块 + 有界自旋 + 挂起续传)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* buf - 载荷(调用方内存,须有效至完成) const void *
|
||
* len - 载荷长度(>0) int
|
||
* dest - 本包目的地址;NULL = 不改 DesIP const struct net_sockaddr_in *
|
||
* caller - 调用方任务句柄(挂起 send 的完成通知目标) TaskHandle_t
|
||
* 出口参数:无
|
||
* 返回值:实际发送字节数(>= 0);NET_PENDING_REGISTERED = 已登记挂起 send;
|
||
* -1 - 失败(NET_ERR_INVAL / NET_ERR_BADF,errno 已记) int
|
||
* 限定条件:netTask 上下文
|
||
* 函数说明:1. dest 非 NULL → 先 SET_DES_IP/SET_DES_PORT,并把 udp_mode 置
|
||
* CLIENT、对端簿记写入 remote_ip_arr/remote_port(芯片静默进
|
||
* 客户端模式是硬件事实,本层必须如实记账,否则收包剥头判错)。
|
||
* 2. 分块与等待语义与 net_send_locked 完全同源(NET_SEND_CHUNK_MAX /
|
||
* NET_SEND_SPIN_MS / NET_SEND_TIMEOUT_MS / 末块不等 SENBUF_FREE /
|
||
* 短写留痕),复用挂起 send 表推进——**不复制一份 UDP 专用自旋**,
|
||
* 两条路径分块粒度漂移就是 TRAP-NET-16 的复发形态。
|
||
* 3. 载荷指针在挂起期间由调用方持有(调用方阻塞在 net_wait_result,
|
||
* 栈与静态缓冲均有效),与 TCP 挂起 send 同一条铁律。
|
||
*/
|
||
int net_udp_sendto_exec(int sockfd, const void *buf, int len,
|
||
const struct net_sockaddr_in *dest, TaskHandle_t caller);
|
||
|
||
/*
|
||
* 函数功能:UDP 接收执行体(netTask 上下文:即时读 / 登记挂起 / 立即拒)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* buf - 调用方缓冲 void *
|
||
* len - 缓冲容量(>0) int
|
||
* flags - NET_MSG_DONTWAIT = 单次非阻塞 int
|
||
* to_ms - 等待预算(0 = 无限等待,须 blocking 属性) uint32_t
|
||
* caller - 调用方任务句柄(挂起完成通知目标) TaskHandle_t
|
||
* 出口参数:无(源地址经 net_udp_outparam_arm 登记的指针直接写回调用方内存)
|
||
* 返回值:实际接收字节数(> 0);NET_ERR_RETURN_1(WOULDBLOCK) = 非阻塞无数据;
|
||
* NET_PENDING_REGISTERED = 已登记挂起;-1 = 失败 int
|
||
* 限定条件:netTask 上下文;控制块在用且为 DGRAM
|
||
* 函数说明:1. 判据 = ch395f_get_recv_len()(芯片事实),readable 仅作省 SPI 的
|
||
* 门(同 net_pending_tick:事件未收到时 net_poll 每轮重读电平型 SINT
|
||
* 会重新置位,U15 探针同源结论)。
|
||
* 2. 服务器模式(udp_mode == SERVER)且本包 >= 8 字节 → 先读 8 字节
|
||
* 信息头解析源地址再读载荷;客户端模式 → 整包都是载荷,源地址 =
|
||
* 本 Socket 固定对端(remote_ip_arr/remote_port)。
|
||
* 3. **一个数据报一次读完**:可用字节 > len 时读满 len 后把剩余部分
|
||
* 读空丢弃(BSD 截断语义;手册 §9.2.4 对服务器模式要求一次性全部
|
||
* 读出)。旧实现只读 min(可用, len) 把半包留在芯片缓冲里,下一次
|
||
* 收包会把上一包的尾巴当成新包头的起点——整条链路的源地址从此错位。
|
||
* 4. 读空后清 readable(旧实现从不清,net_select 会永久报可读)。
|
||
* 5. 无数据 + 阻塞 → 登记挂起(同 fd 已有挂起 / 执行任务自身 → 拒绝,
|
||
* BUSY),netTask 零占用返回;完成条件由 net_udp_tick 判定。
|
||
*/
|
||
int net_udp_recvfrom_exec(int sockfd, void *buf, int len, int flags,
|
||
uint32_t to_ms, TaskHandle_t caller);
|
||
|
||
/*
|
||
* 函数功能:挂起 UDP 接收完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
|
||
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
|
||
* 出口参数:无
|
||
* 返回值:无
|
||
* 限定条件:netTask 上下文
|
||
* 函数说明:逐 fd 按序判定:①fd 已关闭/已非 UDP → 以 NET_ERR_CONNRESET 结束(必通知)
|
||
* ②数据到达 → 读包(含剥头/截断/清 readable)+ 回填源地址 + 以字节数结束
|
||
* ③到点(仅显式预算)→ 以 NET_ERR_TIMEDOUT 结束(调用方 net_recvfrom_to
|
||
* 映射为对外 -2)。三条出口都必须经统一出口通知,漏通知 = 调用方永久挂死。
|
||
*/
|
||
void net_udp_tick(net_sock_t *p_socks);
|
||
|
||
/*
|
||
* 函数功能:清除本 fd 的 UDP 接收槽(不通知;"新 fd 不得继承"与 close 后幂等清理)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* 出口参数:无
|
||
* 返回值:无(越界静默返回)
|
||
* 限定条件:任意上下文(netTask 的 close/socket 分配路径调用)
|
||
*/
|
||
void net_udp_clear(int sockfd);
|
||
|
||
/*
|
||
* 函数功能:取消全部挂起 UDP 接收并逐一通知(网络重建/复位路径)
|
||
* 入口参数:err - 通知的错误码 int
|
||
* 出口参数:无
|
||
* 返回值:无
|
||
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
|
||
*/
|
||
void net_udp_cancel_all(int err);
|
||
|
||
/*
|
||
* 函数功能:关闭前结算本 fd 的挂起 UDP 接收(有数据先送达,无数据则取消)
|
||
* 入口参数:sockfd - 将被关闭的描述符 (0~7) int
|
||
* p_sock - 将被关闭的控制块指针(与 sockfd 对应) net_sock_t *
|
||
* 出口参数:无
|
||
* 返回值:无(无在位挂起时静默返回)
|
||
* 限定条件:netTask 上下文;须在 ch395f 关闭/复位该 Socket 之前调用
|
||
* 函数说明:与 net_pending_settle_close 同一契约——关闭不丢已到达数据
|
||
*/
|
||
void net_udp_settle_close(int sockfd, net_sock_t *p_sock);
|
||
|
||
#endif /* NET_UDP_H */
|