Files
STM32F4-Base/Drivers/BSP/NET/net_udp.h
T
super_agent_bot fff961d803
CI / 静态门禁 (pre-commit) (push) Failing after 49s
CI / 主机侧单测 (MSVC) (push) Failing after 30s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73)
前置来自 #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/任务规划/**。
2026-11-03 18:44:01 +08:00

191 lines
12 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.
/*
* 模块名称: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 */