Files
STM32F4-Base/Drivers/BSP/NET/net_pending.c
T
super_agent_bot a4b8bee209
CI / 静态门禁 (pre-commit) (push) Has been cancelled
CI / 主机侧单测 (MSVC) (push) Has been cancelled
CI / 静态门禁 (pre-commit) (pull_request) Has been cancelled
CI / 主机侧单测 (MSVC) (pull_request) Has been cancelled
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:08:27 +08:00

706 lines
33 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.
/*
* 模块名称:挂起请求表(阻塞 recv / send 挂起模型)—— 实现
* 文件说明:见 net_pending.h;架构与五路语义见
* docs/网络/Net层阻塞模型架构设计.md
* 依赖(全部单向,无反向 include):
* net_types.h(net_sock_t / NET_ERR_* / NET_SOCK_STATE_*)/
* net_socket.h(NET_MAX_SOCKETS / net_set_errno / NET_ERR_RETURN_1 /
* net_pending_active 声明)/ net_pending.h(本模块接口)/
* net_listener.h(net_listener_request / net_listener_tick)/ ch395f.h(RX 读出 / TX 分块写)/ main.h(HAL_GetTick)
* 限定条件:本文件真机专属——模拟器走 sim/net_socket_sim.c,
* simulator/CMakeLists.txt 不编译本文件(同 net_listener.c 先例)
*
* 阻塞 recv / send 挂起模型(结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成
* "无数据 / 发送缓冲未空 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回
* net_pending_tick / net_send_pending_tick(与 D10 重监听重试同节奏,
* net_process_messages 开头每轮调用)
* recv:按 ①数据 ②到点(仅显式预算) ③断连 顺序检查完成条件并通知调用方
* send:按 ①断连 ②推进写块 ③写完 ④无进展超时 顺序推进并通知调用方
* 关闭/网络重建/陈旧重同步走显式 cancel 路径(取消必通知,调用方永不挂死)
* 公开 API 返回契约:recv >0 字节数 / 0 EOF / -1 错误;send >=0 已发字节数(短写由
* 调用方按 ret != len 判定)/ -1 错误(错误码经通知值回传)
* 预算归调用方:timeout_ms=0 即无限等待(无默认预算);挂起超时留痕探针为
* 编译期选项(NET_PENDING_TRIPWIRE,默认 0 = 编译剔除)
*/
#include <stddef.h>
#include "FreeRTOS.h"
#include "task.h"
#include "net_types.h"
#include "net_socket.h"
#include "net_pending.h"
#include "net_listener.h"
#include "ch395f.h"
#include "dbg_log.h"
#include "main.h"
typedef struct {
uint8_t active; /* 1 = 本 fd 有挂起 recv */
uint8_t infinite; /* 1 = 无限等待(无 deadline;0 预算 = 永等) */
#if NET_PENDING_TRIPWIRE
uint8_t tripwire_logged; /* 1 = 本轮挂起已留痕(保证只留一条) */
#endif
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
int len; /* 缓冲容量 */
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算(infinite=1 时无意义) */
uint32_t registered; /* 登记时刻(tick;留痕探针/日志基准) */
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
} net_pending_t;
/* 每 fd 一个挂起(禁止覆盖:覆盖丢失前调用方通知 → 挂死) */
static net_pending_t s_net_pending[NET_MAX_SOCKETS];
/* 执行任务(net_init 捕获;防自通知死锁) */
static TaskHandle_t s_owner_task;
/*
* 函数功能:捕获执行任务句柄(登记守卫据此拒绝自等待死锁)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:执行任务上下文(NET 层 = netTask / 硬件层 = testTask),net_init 顶部
*/
void net_pending_capture_owner(void) {
s_owner_task = xTaskGetCurrentTaskHandle();
}
/*
* 函数功能:通知调用方并清除本 fd 的挂起请求(所有完成/取消路径的公共出口)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* result - 32 位结果原值(>0 字节数 / 0 EOF / 负错误码) int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知)
* 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方直接按通知值
* 取结果);失败记入 netTask 自己的 errno 槽位
*/
static void net_pending_complete(int sockfd, int result) {
/* 本 fd 挂起请求指针 */
net_pending_t *p;
/* 描述符越界:静默返回(防重复通知) */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 取挂起请求 */
p = &s_net_pending[sockfd];
/* 无在位挂起(已结束/已取消):静默返回 */
if (p->active == 0U) {
return;
}
/* 失败结果:记入 netTask 自己的错误码槽位(不再写入跨任务共享全局) */
if (result < 0) {
(void)net_set_errno(result);
}
/* 覆盖式写入通知值 = 结果本身(32 位原值) */
xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite);
/* 清除挂起状态 */
p->active = 0U;
}
/*
* 函数功能:登记一个挂起 recv 请求(net_recv_locked 的阻塞无数据路径)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 调用方接收缓冲(须保持有效至完成) void *
* len - 缓冲容量 int
* to_ms - 本次等待预算(0 = 无限等待) uint32_t
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
* 出口参数:无
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
* -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起)
* 限定条件:netTask 上下文
* 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死);
* 执行任务自身发起的阻塞等待无他人可完成(tick 跑在执行任务内)→ 自等自
* 通知 = 死锁,必须拒绝(netTask 上下文调用方须用 DONTWAIT)
*/
int net_pending_register(int sockfd, void *buf, int len,
uint32_t to_ms, TaskHandle_t caller) {
/* 挂起请求指针 */
net_pending_t *p;
/* 登记时刻(tick) */
uint32_t now;
/* 描述符越界 → BADF */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 取挂起请求 */
p = &s_net_pending[sockfd];
/* 同 fd 已有挂起:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */
if (p->active != 0U) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */
if ((caller == NULL) || (caller == s_owner_task)) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 当前 tick(截止/留痕探针基准) */
now = HAL_GetTick();
/* 标记挂起在位 */
p->active = 1U;
/* 预算形态:0 = 无限等待(无 deadline,tick ② 跳过;留痕探针以登记时刻计) */
p->infinite = (to_ms == 0U) ? 1U : 0U;
#if NET_PENDING_TRIPWIRE
/* 留痕标记清零(每轮挂起只留一条) */
p->tripwire_logged = 0U;
#endif
/* 调用方接收缓冲(须保持有效至完成) */
p->buf = buf;
/* 缓冲容量 */
p->len = len;
/* 截止时刻 = 登记时刻 + 预算(无限等待时不启用,置 0 仅为可观测性) */
p->deadline = (to_ms == 0U) ? 0U : (now + to_ms);
/* 登记时刻(留痕探针基准) */
p->registered = now;
/* 完成通知目标 */
p->caller = caller;
/* 登记成功:分派跳过即时通知 */
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(仅显式预算)/ 断连
* 2. 完成即经 net_pending_complete 通知并清除
* 3. 挂起超时留痕探针(编译期选项 NET_PENDING_TRIPWIRE,默认关):挂起超阈值
* 只 [ERR] 留痕一次("完成逻辑缺位"探针),绝不强制结束、绝不伪造返回值
*/
void net_pending_tick(net_sock_t *p_socks) {
/* 当前 tick */
uint32_t now = HAL_GetTick();
/* Socket 循环索引 */
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 本 fd 挂起请求 */
net_pending_t *p = &s_net_pending[i];
/* 本 fd 控制块 */
net_sock_t *p_sock = &p_socks[i];
/* 无挂起请求:跳过 */
if (p->active == 0U) {
continue;
}
/* fd 已关闭 */
if (p_sock->in_use == 0U) {
/* fd 已关闭(正常路径在 close 内先结束;此处兜底,防任何路径漏掉通知) */
net_pending_complete(i, NET_ERR_CONNRESET);
continue;
}
/* ① 数据到达(数据优先)——readable 门控:常见无数据路径零额外 SPI 事务
* (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位) */
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) ||
(p_sock->state == NET_SOCK_STATE_UDP_OPEN)) {
/* 可读标志置位 */
if (p_sock->readable != 0U) {
/* 查询芯片侧可用字节数 */
int avail = ch395f_get_recv_len((uint8_t)i);
/* 有数据 */
if (avail > 0) {
/* 本次读取量 = min(可用, 缓冲容量) */
int rd = (avail > p->len) ? p->len : avail;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)rd;
/* 数据已消费,清可读标志 */
p_sock->readable = 0U;
/* 结束:以字节数通知 */
net_pending_complete(i, rd);
continue;
}
/* 虚假标志:缓冲无数据,继续等待 */
p_sock->readable = 0U;
}
}
/* ② 到点(仅显式预算登记;无限等待登记无 deadline):
* 超时是调用方策略,net_recv_to 的预算在此兑现,不是伪造。
* tick 差值按有符号比较,抗回绕) */
if ((p->infinite == 0U) && ((int32_t)(now - p->deadline) >= 0)) {
/* 结束:超时(错误码由 net_pending_complete 记入本任务槽位) */
net_pending_complete(i, NET_ERR_TIMEDOUT);
continue;
}
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。
* 判据同 net_recv_locked(监听生命周期 P1 不变量 I):`state == CLOSED ||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
* (lftpd 阻塞 recv 的"消费者卡死"原型)。无限等待登记没有 ②,
* 漏 ③ = 永久挂死(留痕探针只留痕、不唤醒)——
* ③ 与显式 cancel 是无限 recv 的全部唤醒责任方,勿动判据。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
/* 查询残留字节数 */
int rem = ch395f_get_recv_len((uint8_t)i);
/* 有残留数据:按数据完成 */
if (rem > 0) {
/* 本次读取量 = min(残留, 缓冲容量) */
int rd = (rem > p->len) ? p->len : rem;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)rd;
/* 数据已消费,清可读标志 */
p_sock->readable = 0U;
/* 结束:以字节数通知 */
net_pending_complete(i, rd);
} else {
/* 启用自动重监听:登记重建请求(动作在 net_listener_tick 一处)。
* 原内层 `state == CLOSED` 门已**上收到 tick**——它防的是"对已回
* LISTENING 的 fd 再 open+listen → 因 USE 错误走进关闭分支、关掉
* 正听着的 Socket"(U12 同源),属执行侧安全不变量,不由登记点复述。
* 监听生命周期 P2 修正:**保留本处即时登记** —— 它只在有**挂起 recv**
* 时到达(`p->active != 0`),属"消费者在等这条连接结局"的
* 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */
if (p_sock->auto_relisten != 0U) {
/* 非阻塞:下一轮 net_listener_tick 执行(入口收敛到 request) */
net_listener_request(i, NET_LISTENER_REQ_PENDING);
}
/* TRAP-NET-18 待办 #3:异常释放(TIMEOUT/RST)不是正常 EOF(对端未发正常 FIN)
* → 按连接重置结束:应用(lftpd STOR)对异常结束回 5xx 而非误报 226;
* 正常 FIN(DISCONNECT)仍回 0(在途 FIN 与正常完成不可区分——协议边界,见 TRAP-NET-18 记录) */
/* 异常释放:按连接重置结束(回 5xx,不误报 226) */
if (p_sock->released_abnormal != 0U) {
/* 异常释放(错误码由 net_pending_complete 记入本任务槽位) */
net_pending_complete(i, NET_ERR_CONNRESET);
} else {
/* 正常 EOF:以 0 结束 */
net_pending_complete(i, 0);
}
}
continue;
}
/* 其他状态(CONNECTING/LISTENING/…):保持等待状态迁移——CONNECT→ESTABLISHED
* 是合法迁移,不做状态推断取消,避免误杀正在建链的等待;陈旧 resync 由显式 cancel 处理 */
/* ④ 挂起超时留痕探针(编译期选项 NET_PENDING_TRIPWIRE,默认关):挂起超
* 阈值 = "完成逻辑缺位"探针 → 只留一条 [ERR],**绝不强制结束、绝不伪造
* TIMEDOUT 返回值**(强制结束会把完成路径缺陷伪装成普通超时,调用方
* 拿到假结果、缺陷永远潜伏)。判据:无限登记按登记时刻计;显式登记按
* deadline 计(② 正常不会让它越过阈值,能到这里本身就是完成逻辑缺位)。 */
#if NET_PENDING_TRIPWIRE
if (p->tripwire_logged == 0U) {
/* 探针基准时刻:无限 = 登记时刻;显式 = 截止时刻 */
uint32_t base = (p->infinite != 0U) ? p->registered : p->deadline;
if ((int32_t)(now - base) >= (int32_t)NET_RECV_TRIPWIRE_MS) {
/* 留痕(挂起已持续毫秒数,自基准起算) */
DBG_ERROR("sock%d recv pending %ums (base=%s): completion source missing",
i, (unsigned)((int32_t)now - (int32_t)base),
(p->infinite != 0U) ? "infinite" : "deadline");
/* 只留痕一次(每轮挂起一条,防日志风暴) */
p->tripwire_logged = 1U;
}
}
#endif
}
}
/*
* 函数功能:查询本 fd 是否有在途挂起 recv —— 宿主能力,供 net_listener 用
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
* 函数说明:1. 监听生命周期模块化:net_listener_tick 用它跳过"重开 Socket
* 会摧毁在途连接"的 fd(挂起 recv 保险,原为直接读 s_net_pending[i].active)
* 2. 声明在 net_pending.h(配对头,规范 S17 2.2)
*/
int net_pending_active(int sockfd) {
/* 描述符越界:视为无挂起 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return (s_net_pending[sockfd].active != 0U) ? 1 : 0;
}
/*
* 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无
* 限定条件:无
* 函数说明:1. 静默清理不发通知:close 路径先经 settle 通知,本调用仅保证状态归零(幂等)
* 2. 新建 fd 时调用,防复用槽位继承上一条连接的挂起请求
*/
void net_pending_clear(int sockfd) {
/* 描述符越界:直接返回 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 挂起状态清零(静默,不发通知) */
s_net_pending[sockfd].active = 0U;
}
/*
* 函数功能:取消本 fd 的挂起请求并通知调用方(显式路径:陈旧 resync 等)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* err - 通知的错误码(如 NET_ERR_CONNRESET) int
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文
* 函数说明:经 net_pending_complete 以指定错误码通知调用方(取消必通知,防挂死)
*/
void net_pending_cancel(int sockfd, int err) {
/* complete 内部校验范围与在位(未在位则静默返回) */
net_pending_complete(sockfd, err);
}
/*
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
* 入口参数:err - 通知的错误码 int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
* 函数说明:网络重建(net_stack_bring_up)前调用,保证所有在位调用方均被唤醒
*/
void net_pending_cancel_all(int err) {
/* 逐 fd 遍历 */
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取消并通知(未在位则静默跳过) */
net_pending_complete(i, err);
}
}
/*
* 函数功能:关闭前先结束本 fd 的挂起 recv——把结果回传并唤醒调用方(有数据先
* 送达,无数据则取消),再关闭 Socket(per-fd 串行:先完成、再关闭)
* 入口参数:sockfd - 将被关闭的描述符 (0~7) int
* p_sock - 将被关闭的 Socket 控制块指针(与 sockfd 对应) net_sock_t *
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用
* 函数说明:先排空尝试——关闭不丢已到达数据(芯片缓冲在 close 前可读);
* 有数据按字节数通知,否则取消(NET_ERR_CONNRESET)
*/
void net_pending_settle_close(int sockfd, net_sock_t *p_sock) {
/* 通知用结果值(默认为取消) */
int val;
/* 芯片 RX 可用字节数 */
int avail;
/* 描述符越界或无在位挂起:静默返回 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
if (s_net_pending[sockfd].active == 0U) {
return;
}
/* 默认结果 = 取消(CONNRESET) */
val = NET_ERR_CONNRESET;
/* 查询芯片 RX 可用字节(缓冲内数据在 close 前可读) */
avail = ch395f_get_recv_len((uint8_t)sockfd);
/* 数据已到达:排空,关闭不丢数据 */
if (avail > 0) {
/* 本次读取量 = min(可用, 缓冲容量) */
int rd = (avail > s_net_pending[sockfd].len) ? s_net_pending[sockfd].len : avail;
/* 读入调用方缓冲区 */
ch395f_read_recv_buf((uint8_t)sockfd, (uint8_t *)s_net_pending[sockfd].buf, (uint16_t)rd);
/* 累计接收字节统计 */
p_sock->recv_bytes += (uint32_t)rd;
/* 数据已消费,清可读标志 */
p_sock->readable = 0U;
/* 有数据:以字节数通知 */
val = rd;
}
/* 结束(必通知) */
net_pending_complete(sockfd, val);
}
/* ================= 挂起 send 请求表(SEND pending,二期) ================= */
typedef struct {
uint8_t active; /* 1 = 本 fd 有挂起 send */
const void *buf; /* 调用方缓冲基址(仅指针,须保持有效至完成) */
int len; /* 本次发送总长度(含登记前已写部分) */
int sent; /* 已写入芯片的字节数(进度;登记时可 > 0) */
uint32_t deadline; /* 无进展超时时刻 = 上次成功写块时刻 + NET_SEND_TIMEOUT_MS */
uint32_t registered; /* 登记时刻(tick;日志基准) */
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
} net_send_pending_t;
/* 每 fd 一个挂起 send(与 recv 表独立:收发两方向互不阻塞) */
static net_send_pending_t s_net_send[NET_MAX_SOCKETS];
/*
* 函数功能:通知调用方并清除本 fd 的挂起 send(所有完成/取消路径的公共出口)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* result - 结果原值(本模型恒为**已发字节数** >= 0) int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知)
* 函数说明:send 结果恒 >= 0(短写语义),故不在此改写错误码——取消/超时的原因
* 由各调用路径在调用本函数**之前**经 net_set_errno 记录
*/
static void net_send_pending_complete(int sockfd, int result) {
/* 本 fd 挂起请求指针 */
net_send_pending_t *p;
/* 描述符越界:静默返回(防重复通知) */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 取挂起请求 */
p = &s_net_send[sockfd];
/* 无在位挂起(已结束/已取消):静默返回 */
if (p->active == 0U) {
return;
}
/* 覆盖式写入通知值 = 已发字节数(32 位原值) */
xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite);
/* 清除挂起状态 */
p->active = 0U;
}
/*
* 函数功能:登记一个挂起 send 请求(net_send_locked 的"等发送缓冲"超预算路径)
* 入口参数:见 net_pending.h
* 出口参数:无
* 返回值:NET_PENDING_REGISTERED - 登记成功;-1 - 拒绝(见头注释)
* 限定条件:netTask 上下文
* 函数说明:每 fd 至多一个挂起 send(禁止覆盖——覆盖丢失前调用方通知 = 永久挂死);
* 执行任务自身发起(net_send_sock 阻塞用法)无他人可完成 → 拒绝(自等死锁)
*/
int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
TaskHandle_t caller) {
/* 挂起请求指针 */
net_send_pending_t *p;
/* 登记时刻(tick) */
uint32_t now;
/* 描述符越界 → BADF */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* 参数非法(缓冲无效 / 长度非正 / 进度越界)→ INVAL */
if ((buf == NULL) || (len <= 0) || (sent < 0) || (sent >= len)) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 取挂起请求 */
p = &s_net_send[sockfd];
/* 同 fd 已有挂起 send:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */
if (p->active != 0U) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */
if ((caller == NULL) || (caller == s_owner_task)) {
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 当前 tick(停滞计时基准) */
now = HAL_GetTick();
/* 标记挂起在位 */
p->active = 1U;
/* 调用方发送缓冲基址(须保持有效至完成) */
p->buf = buf;
/* 本次发送总长度 */
p->len = len;
/* 进度起点(登记前已写入芯片的字节数) */
p->sent = sent;
/* 无进展超时起点(NET_SEND_TIMEOUT_MS = 0 表示不限,同快速路径口径) */
p->deadline = (NET_SEND_TIMEOUT_MS > 0) ? (now + (uint32_t)NET_SEND_TIMEOUT_MS) : 0U;
/* 登记时刻(日志基准) */
p->registered = now;
/* 完成通知目标 */
p->caller = caller;
/* 登记成功:分派跳过即时通知 */
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:判"本 Socket 的发送会话是否已不可继续"(TCP/UDP 统一判据)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:1 - 不可继续(应报 CONNRESET/NOTCONN);0 - 可继续 int
* 限定条件:p_sock 非空;任意上下文(纯读控制块)
* 函数说明:TCP 的门是 ESTABLISHED,UDP 的门是 UDP_OPEN(芯片侧由 net_bind 打开)。
* 判据只写这一处:net_send_locked 的入口门与两处中途检查、
* net_send_pending_tick 的 ① 都用它——两条发送路径各自复述一遍
* "state != ESTABLISHED"正是 UDP 接入时最容易漏改的形态。
*/
int net_send_state_down(const net_sock_t *p_sock) {
if (p_sock->type == NET_SOCK_DGRAM) {
return (p_sock->state != NET_SOCK_STATE_UDP_OPEN) ? 1 : 0;
}
return (p_sock->state != NET_SOCK_STATE_ESTABLISHED) ? 1 : 0;
}
/*
* 函数功能:挂起 send 推进与完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:1. 逐 fd 按序:①会话不可继续(TCP 断连 / UDP 芯片侧被关)→ 以已发字节数结束
* (剩余字节不可能送达)
* 2. ②推进:发送缓冲空闲则写下一块(每轮至多一块——写完即清 send_ready,
* 下一块须等 SENBUF_FREE 中断重新置位,由 net_poll 刷新)
* 3. ③全部写完 → 以总长度结束;④无进展超时 → 以已发字节数结束
* 4. 结果恒为已发字节数(短写由调用方按 `ret != len` 判定)
* 5. 停滞计时按**无进展**刷新:大文件传输总时长必然超过单块预算,
* 按总时长判会误杀(与 recv 侧"到点"语义的差异点)
*/
void net_send_pending_tick(net_sock_t *p_socks) {
/* Socket 循环索引 */
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 本 fd 挂起请求 */
net_send_pending_t *p = &s_net_send[i];
/* 本 fd 控制块 */
net_sock_t *p_sock = &p_socks[i];
/* 无挂起请求:跳过 */
if (p->active == 0U) {
continue;
}
/* fd 已关闭(正常路径在 close 内先取消;此处兜底,防任何路径漏掉通知) */
if (p_sock->in_use == 0U) {
net_send_pending_complete(i, p->sent);
continue;
}
/* ① 会话已不可继续 → 剩余字节不可能送达,按已发字节数结束。
* 判据见 net_send_state_down(TCP:state 离开 ESTABLISHED 后即便再回到
* ESTABLISHED 也是**新会话**,旧会话剩余字节绝不能投到新连接上;
* UDP:芯片侧 Socket 被关/复位即不可继续)。session_dead 一并覆盖
* (net_types.h:TIMEOUT/DISCONNECT 两条路径都同时置 CLOSED,此处为
* 与 recv 侧判据对齐的冗余保险) */
if ((net_send_state_down(p_sock) != 0) || (p_sock->session_dead != 0U)) {
DBG_ERROR("sock%d send pending aborted (%d/%d): session down",
i, p->sent, p->len);
(void)net_set_errno(NET_ERR_CONNRESET);
net_send_pending_complete(i, p->sent);
continue;
}
/* ② 推进:发送缓冲空闲则写下一块 */
if ((p->sent < p->len) && (p_sock->send_ready != 0U)) {
/* 本次写块长度 = min(剩余, 硬件发送缓冲上限) */
int chunk = p->len - p->sent;
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
/* 写入芯片发送 FIFO(与快速路径同一分块粒度,TRAP-NET-16) */
ch395f_write_send_buf((uint8_t)i, (uint8_t *)p->buf + p->sent, (uint16_t)chunk);
/* 标记发送缓冲占用中(等 SENBUF_FREE 中断重新置位) */
p_sock->send_ready = 0U;
/* 累计进度与统计 */
p->sent += chunk;
p_sock->send_bytes += (uint32_t)chunk;
/* 进度刷新:停滞判定按"无进展"而非"总时长" */
p->deadline = (NET_SEND_TIMEOUT_MS > 0)
? (HAL_GetTick() + (uint32_t)NET_SEND_TIMEOUT_MS) : 0U;
}
/* ③ 全部写完:以总长度结束(调用方见 ret == len 即成功) */
if (p->sent >= p->len) {
net_send_pending_complete(i, p->sent);
continue;
}
/* ④ 无进展超时:以已发字节数结束(短写;调用方须检查返回值) */
if ((NET_SEND_TIMEOUT_MS > 0) &&
((int32_t)(HAL_GetTick() - p->deadline) >= 0)) {
DBG_ERROR("sock%d send pending stall (%d/%d)", i, p->sent, p->len);
(void)net_set_errno(NET_ERR_TIMEDOUT);
net_send_pending_complete(i, p->sent);
continue;
}
}
}
/*
* 函数功能:清除本 fd 的挂起 send(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无
* 限定条件:无
* 函数说明:静默清理不发通知:close / 重建路径先经 cancel 通知,本调用仅保证状态归零(幂等)
*/
void net_send_pending_clear(int sockfd) {
/* 描述符越界:直接返回 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 挂起状态清零(静默,不发通知) */
s_net_send[sockfd].active = 0U;
}
/*
* 函数功能:取消本 fd 的挂起 send 并通知调用方(close / drop_conn / resync / 重建)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* err - 取消原因(记入 netTask 错误码槽位 + 日志) int
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文
* 函数说明:以**已发字节数**结束(已写入芯片的字节不回收——对端可能已收到;未发部分
* 放弃,由调用方按短写处理)。取消必通知,否则调用方永久挂死
*/
void net_send_pending_cancel(int sockfd, int err) {
/* 本 fd 挂起请求指针(只读:完成经 net_send_pending_complete 按 fd 操作) */
const net_send_pending_t *p;
/* 描述符越界:静默返回 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
/* 取挂起请求 */
p = &s_net_send[sockfd];
/* 无在位挂起:静默返回 */
if (p->active == 0U) {
return;
}
/* 留痕:取消原因 + 进度(短写程度) */
DBG_ERROR("sock%d send pending cancel (%d/%d, err=%d)", sockfd, p->sent, p->len, err);
/* 原因记入 netTask 自己的错误码槽位(结果 >= 0 不会被 complete 代记) */
(void)net_set_errno(err);
/* 结束:以已发字节数通知 */
net_send_pending_complete(sockfd, p->sent);
}
/*
* 函数功能:取消全部挂起 send 并逐一通知(网络重建路径)
* 入口参数:err - 取消原因 int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
* 函数说明:网络重建(net_stack_bring_up → net_init)前调用,保证所有在位调用方均被唤醒
*/
void net_send_pending_cancel_all(int err) {
/* 逐 fd 遍历 */
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取消并通知(未在位则静默跳过) */
net_send_pending_cancel(i, err);
}
}
/*
* 函数功能:查询本 fd 是否有在途挂起 send —— 宿主能力,供 net_listener 用
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
* 函数说明:net_listener_tick 用它跳过"重开 Socket 会摧毁在途传输"的 fd
* (挂起 send 保险;与 net_pending_active 的挂起 recv 保险并列)
*/
int net_send_pending_active(int sockfd) {
/* 描述符越界:视为无挂起 */
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return (s_net_send[sockfd].active != 0U) ? 1 : 0;
}