Files
STM32F4-Base/test/host/net_socket/ns_s4_state.c
T
wandering1 a3c6e89378 fix(网络): 主动拆会话登记即返——drop 路径不再占用 netTask(登记簿 N-17 收口)
道F 批 2 / task-17。定位 + 单点收口,不开 net 层跨上下文大口子。

【定位(链路,全部代码可复核)】
探针挂起 30s → time_sync.c:149 预算耗尽 → :187 net_drop_conn()。NET_MSG_
DROP_CONN 处理器(net_drop_conn_locked)内同步跑 net_sock_reset_sync
(QUARANTINE)(2ms 节奏、400ms 底/600ms 顶,自家注释原记「等待期 netTask
有界占用 ~400-600ms」):netTask 的 1ms 主循环冻结在同一迭代 → NET 位
停摆;串行总线整窗停摆 → UI 推流/FTP/104 在途消息回执顺延;对时调用方
睡在 net_wait_result 等回执期间无分片心跳 → 五位同窗缺席 → 连续 8 个
100ms 抽样停喂 → 声明 [WD] task stall(V5 时代 req=0x37、快照只剩 bit0,
即日志 mask=0x00000001)。被动断连/EOF 走 net_listener_tick 异步沉降机
(D10 条令)故纯 FTP 负载 0 stall——只有主动 drop 留在同步原语里。

【修复】监听型 fd 的芯片拆除改为 net_listener_settle(QUARANTINE) +
net_listener_request(REQ_DROP) 登记即返,拆链/排渣/等 CLOSED/重建全部
交 net_listener_tick 30ms 节奏——与被动释放同一台机器(E16 台架实证该
自建序列 RST 10/10、FIN 5/5)。非监听 client fd 无监听可保,同步形态
原样保留(全仓零生产调用者)。返回值语义显式改为「拆除与重建已登记」
(net_socket.h 注释按 lead 要求写明,防未来调用方按旧语义写)。
APP_ASSERT(S12 试点)分派门与断言位置未动,登记时序下仍成立。

【评估否决留档(lead 要求论证入文档)】「缩短重建重试 5×2s」不采:
该退避在 time_sync_open_listener() bind/listen 失败路径、已被
time_sync_delay_fed() 分片喂保护、不在缺口链上;缩短反撞 TRAP-NET-09
沉降窗。全文见 docs/网络/网络自愈架构设计.md §4.6(已改写)。

【竞态与信源核对】① 主路径置 CLOSED 后迟到的 DISCONNECT/TIMEOUT 被
net_events 状态门拦截(只认活会话态),不会把刚登记的 QUARANTINE 降级;
② 零新增读清点——全局寄存器唯一读者仍 net_poll,隔离窗两处本 Socket
读清是 net_listener_tick 原位,§4.10 三条准入判据不触发;③ E13 物理
边界(对端不配合 FIN_WAIT_2 长尾)逐字不变,只移动等待发生的位置。

【测试】net_socket 主机套件 457/457:TC-NETSOCK-240f/321b/321b2/460e
重锚到登记式(settle+REQ_DROP、rst 机零启动、tick 预算 ≤20、无 [ERR]),
新增 321g/h/i + 460f 钉住 client fd 旧同步形态。全量 --all-files 40 绿。

【体积增量(vs scripts/ram_baseline.tsv 2026-10-25 行)】
ER_IROM1 237328→237344(+16 B);RW_IRAM1 75792(+0);ZI 0。
@build 0 错误 0 警告(Code=215948 RO=21396 RW=1232 ZI=131984)。

gate: only mem-budget/mem-numbers pending lead ledger backfill
test-coverage 本条**绿**(已覆盖行净增,未动用裁决③的覆盖率例外——
旧同步路径行经 client fd 新用例继续被覆盖,净差非负)。其余含
id-check/doc-links/全部主机单测绿。仅 mem-numbers/mem-budget 两红 =
台账 +16B 漂移待回填(lead 单写者禁区)。

【越界申报】① test/host/net_socket/{ns_s3_msg,ns_s4_state,ns_s5_io}.c
(批准③同类:只动本任务相关用例,未碰他人行);② docs/网络/
网络自愈架构设计.md §4.6 改写(lead 本任务指令明示)。登记簿 N-17
销项与 README 波次行归 lead;docs/内存管理 数字未碰(禁区)。

台架:V5 复跑判据(e02_probe + FTP ≥30min 零 stall / 零复位)属网络
对端类——待 lead 排窗,步骤清单随 done 消息发出。
2026-10-25 10:08:46 +08:00

362 lines
17 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.
/*
* 分片 4:net_poll 中断分派、监听/可读闸门、close 与 drop_conn 的拆除序列
*
* 这一片钉的是 AGENTS 模块约定里**没有文档就无法发现**的那条芯片约束:
* 所有 CH395F 中断在 net_poll() 内经 GET_GLOB_INT_STATUS 集中读取并清除,不得在
* ISR 或其他地方单独处理。全局寄存器整体读即清、芯片无按位清除手段,而 net_poll
* 只在全局位为 1 时才读该 Socket 的 SINT → 顺手全量清一遍等于把别的在用 Socket 的
* 门焊死(B3 实证,详见 docs/网络/网络自愈架构设计.md §4.10)。
*
* 断言手法(刻意不用「读了几次」这种弱口径):GET_SOCK_INT_STATUS 是**读即清**,
* 所以给每个 Socket 预置互不相同的挂起位图,跑一轮 net_poll 后看哪些位图被清掉了
* ——被清=被读过。这比数调用次数更硬:它直接照出「门有没有被焊死」。
*
* 创建日期:2026-10-24
*/
/* 预置:给每个 Socket 挂一个只有自己才有的 SINT 位图(只用低 6 位,避开保留位) */
static void prime_sint_masks(void)
{
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
g_fake.int_status[i] = (uint8_t)(0x01U << (i % 6));
}
g_chip.int_status_calls = 0;
}
/* TC-NETSOCK-300 net_poll 的前置闸门 */
static void tc_300_poll_gates(void)
{
/* 1) 未初始化:直接 0,连引脚都不该读 */
reset_all();
g_ns_pump = net_process_messages;
CHECK(net_poll() == 0, "TC-NETSOCK-300a uninitialized -> 0 events");
CHECK(g_ns_poll_calls == 0U && g_fxchip.glob_calls == 0,
"TC-NETSOCK-300b uninitialized must not read the INT# pin nor the global interrupt register (saves SPI)");
/* 2) 初始化了但 INT# 为高(无中断待处理):不读全局寄存器 */
bring_up();
g_ns_int_pin_set = 1;
CHECK(net_poll() == 0, "TC-NETSOCK-300c INT# high -> 0 events");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-300d with INT# high no global interrupt read may be issued (pin polling exists to save that step)");
/* 3) INT# 低但全局状态为 0:读了全局一次,且不进 Socket 循环 */
bring_up();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob(0U);
CHECK(net_poll() == 0, "TC-NETSOCK-300e all global bits clear -> 0 events");
CHECK(g_fxchip.glob_calls == 1 && g_chip.int_status_calls == 0,
"TC-NETSOCK-300f global register read exactly once and it stops on 0 (no per-socket sweep)");
reset_all();
}
/* TC-NETSOCK-301 门控精度:只读全局位置位的那个 Socket 的 SINT(B3 的正面表述) */
static void tc_301_poll_only_gated_sockets(void)
{
int i;
int a;
int b;
bring_up();
a = mk_established(NET_SOCK_STREAM);
b = mk_established(NET_SOCK_STREAM);
(void)mk_established(NET_SOCK_STREAM);
CHECK(a == 0 && b == 1, "TC-NETSOCK-301a precondition: three fds in use");
prime_sint_masks();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (1 + 4))); /* Socket n 的位 = bit(n+4) */
(void)net_poll();
CHECK(g_fake.int_status[0] == (uint8_t)(0x01U << 0) &&
g_fake.int_status[2] == (uint8_t)(0x01U << 2),
"TC-NETSOCK-301b pending bitmaps of unlit sockets 0/2 stay uncleared (read-clear means read, still there means not read)");
CHECK(g_fake.int_status[1] == 0U, "TC-NETSOCK-301c the lit socket 1 got read (bitmap cleared)");
CHECK(g_chip.int_status_calls == 1, "TC-NETSOCK-301d the whole round issued exactly one SINT read");
/* 缓存刷新环是另一回事:它对所有在用 fd 都要刷(单一事实源 I1) */
CHECK(g_fxchip.rst_poll_calls == 3,
"TC-NETSOCK-301e every round steps sock_reset for all 3 in-use fds (IDLE slots cost zero SPI)");
(void)net_poll();
CHECK(g_fxchip.rst_poll_calls == 6,
"TC-NETSOCK-301f another round adds 3 more: the refresh loop is not gated by global bits (events vs state split)");
reset_all();
/* 高位字节:Socket 4~7 的全局位在 bit8~bit11 */
bring_up();
for (i = 0; i < 5; i++) {
(void)mk_established(NET_SOCK_STREAM);
}
prime_sint_masks();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (4 + 4)));
(void)net_poll();
CHECK(g_fake.int_status[4] == 0U && g_fake.int_status[3] == (uint8_t)(0x01U << 3),
"TC-NETSOCK-301g sockets 4-7 are located in the high byte bit(n+4)=bit8..11 (a wrong shift never sees their events)");
reset_all();
}
/* TC-NETSOCK-302 六种 SINT 位各自的分派与事件计数 */
static void tc_302_poll_event_dispatch(void)
{
static const uint8_t bits[6] = {
CH395F_SINT_STAT_CONNECT, CH395F_SINT_STAT_DISCONNECT, CH395F_SINT_STAT_RECV_OK,
CH395F_SINT_STAT_SEND_OK, CH395F_SINT_STAT_SOCK_TIMEOUT, CH395F_SINT_STAT_SENBUF_FREE
};
int k;
int fd;
for (k = 0; k < 6; k++) {
int expect = (bits[k] == CH395F_SINT_STAT_SENBUF_FREE) ? 0 : 1;
int n;
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = bits[k];
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
n = net_poll();
CHECK(n == expect, "TC-NETSOCK-302a event counting distinguishes bit kinds (SENBUF_FREE is a state bit, not an event)");
switch (bits[k]) {
case CH395F_SINT_STAT_CONNECT:
CHECK(g_peers.evt_connect_calls == 1, "TC-NETSOCK-302b CONNECT -> handle_connect_event");
break;
case CH395F_SINT_STAT_DISCONNECT:
CHECK(g_peers.evt_disconnect_calls == 1, "TC-NETSOCK-302c DISCONNECT -> handle_disconnect_event");
break;
case CH395F_SINT_STAT_RECV_OK:
CHECK(g_peers.evt_recv_calls == 1 && s_net_socks[fd].readable == 1U,
"TC-NETSOCK-302d RECV_OK -> handle_recv_event sets the readable level");
break;
case CH395F_SINT_STAT_SEND_OK:
CHECK(g_peers.evt_send_calls == 1, "TC-NETSOCK-302e SEND_OK -> handle_send_event");
break;
case CH395F_SINT_STAT_SOCK_TIMEOUT:
CHECK(g_peers.evt_timeout_calls == 1, "TC-NETSOCK-302f TIMEOUT -> handle_timeout_event");
break;
default:
CHECK(s_net_socks[fd].send_ready == 1U && g_peers.evt_send_calls == 0,
"TC-NETSOCK-302g SENBUF_FREE only sets send_ready (no event, never mistaken for SEND_OK)");
break;
}
CHECK(g_peers.evt_connect_calls + g_peers.evt_disconnect_calls +
g_peers.evt_recv_calls + g_peers.evt_send_calls +
g_peers.evt_timeout_calls <= 1,
"TC-NETSOCK-302h one incoming bit drives exactly one handler (no cross-talk)");
}
/* 同一次 SINT 里多个位 → 多个 handler 都被叫到,事件数叠加 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = (uint8_t)(CH395F_SINT_STAT_RECV_OK | CH395F_SINT_STAT_DISCONNECT);
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
CHECK(net_poll() == 2, "TC-NETSOCK-302i multiple event bits in one batch: each counted (the later bit is not swallowed)");
CHECK(g_peers.evt_recv_calls == 1 && g_peers.evt_disconnect_calls == 1,
"TC-NETSOCK-302j two bits each reach their own handler");
reset_all();
}
/* TC-NETSOCK-303 PHY 变更位经驱动 feed 落到链路缓存(F-DRV-13 接线) */
static void tc_303_phy_feed(void)
{
bring_up();
g_ns_int_pin_set = 0;
g_fx.phy_status = CH395F_PHY_DISCONN; /* 下一次 feed 读到断开 */
ch395f_fake_push_glob(CH395F_GINT_STAT_PHY_CHANGE);
(void)net_poll();
CHECK(g_fake.link_up == 0U && g_fake.link_change == 1U,
"TC-NETSOCK-303 PHY_CHANGE -> driver re-reads PHY and counts one link change (cable pull is no longer silent)");
reset_all();
}
/* TC-NETSOCK-310 监听门控 net_listener_armed 的三支放行与全部挡下形态 */
static void tc_310_listener_armed(void)
{
int fd;
reset_all();
CHECK(net_listener_armed(-1) == 0 && net_listener_armed(NET_MAX_SOCKETS) == 0,
"TC-NETSOCK-310a out of range fd -> 0 (pure RAM read, no RPC)");
CHECK(net_listener_armed(0) == 0, "TC-NETSOCK-310b unallocated fd -> 0");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310c ESTABLISHED (pending-accept level) must pass: i104 bench proved starvation without it");
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310d LISTENING must pass: the missed-accept probe lives inside net_accept_locked, blocking it never runs");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 7003U;
s_net_socks[fd].readable = 1U;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310e CLOSED plus auto_relisten plus leftover data -> pass (keeps closed-with-readable accept alive)");
s_net_socks[fd].readable = 0U;
CHECK(net_listener_armed(fd) == 0,
"TC-NETSOCK-310f CLOSED with no leftover must be blocked (else every round in that window sends an RPC and the watchdog starves)");
s_net_socks[fd].readable = 1U;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310g CLOSED on a non-listener fd does not pass");
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310h a half-built listener without a bound port does not pass");
s_net_socks[fd].state = NET_SOCK_STATE_CONNECTING;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310i CONNECTING is not a listening state -> 0");
reset_all();
}
/* TC-NETSOCK-311 recv 门控 net_can_read:非 ESTABLISHED 一律放行 */
static void tc_311_can_read(void)
{
int fd;
reset_all();
CHECK(net_can_read(0) == 1,
"TC-NETSOCK-311a dead descriptor -> 1: let recv report BADF instead of hiding a missing connection");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 0, "TC-NETSOCK-311b ESTABLISHED with nothing readable -> 0");
s_net_socks[fd].readable = 1U;
CHECK(net_can_read(fd) == 1, "TC-NETSOCK-311c readable set -> 1");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 1,
"TC-NETSOCK-311d connection no longer ESTABLISHED -> recv must pass once to fetch EOF or the error");
reset_all();
}
/* TC-NETSOCK-320 close 的拆除序列:快路径(芯片已 CLOSED)与慢路径(走复位机) */
static void tc_320_close_two_paths(void)
{
int fd;
/* 快路径:芯片已 CLOSED → 只清本 Socket 残留事件,不发复位 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_CLOSED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320a close succeeds (fast path)");
CHECK(g_fxchip.cmd_done_calls >= 1 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-320b already CLOSED -> reset skipped (saves a pointless 100-600ms occupation)");
CHECK(g_fxchip.sock_events_calls >= 1,
"TC-NETSOCK-320c the fast path still clears this socket leftover events (else the next connection sees a false immediate drop)");
CHECK(g_fxchip.glob_calls == 0, "TC-NETSOCK-320d zero global reads throughout (F-NET-7)");
reset_all();
/* 慢路径:芯片还 OPEN → 走 sock_reset,且必须是 GRACEFUL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320e close succeeds (slow path)");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 0U,
"TC-NETSOCK-320f slow path uses GRACEFUL (100ms floor for a normal release), not QUARANTINE");
CHECK(g_fx.rst_state[fd] == (uint8_t)CH395F_FAKE_RST_IDLE,
"TC-NETSOCK-320g the reset machine ends back at IDLE (slot reusable, never stranded)");
reset_all();
/* 复位启动被拒(槽位已在运行)→ close 仍放行,只留痕 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_state[fd] = (uint8_t)CH395F_FAKE_RST_DRAINED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320h close still succeeds when starting the reset fails (do not block the caller)");
CHECK(g_fxchip.rst_begin_reject == 1 && g_ns_error_count >= 1,
"TC-NETSOCK-320i a failed reset must leave an [ERR] line for the reconciler/pump to take over");
reset_all();
}
/* TC-NETSOCK-321 drop_conn:监听型 = 登记式(settle QUARANTINE + REQ_DROP,
* N-17 起 netTask 零占用);非监听 client fd 保留同步排渣;两者都不动监听簿记 */
static void tc_321_drop_conn_settle(void)
{
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].readable = 1U;
s_net_socks[fd].lost_conn_seen = 3U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn succeeds");
CHECK(g_peers.listener_settle_calls == 1 && g_peers.last_settle_fd == fd &&
g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE,
"TC-NETSOCK-321b listener fd: abnormal-release isolation REGISTERED for the tick machine (TRAP-NET-18 QUARANTINE semantics survive, executed by net_listener_tick)");
CHECK(g_fxchip.rst_begin_calls == 0 && g_ns_error_count == 0,
"TC-NETSOCK-321b2 N-17: the handler starts no synchronous reset and logs nothing - netTask keeps its ~1ms beat");
CHECK(s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (N-30)");
CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U &&
s_net_socks[fd].in_use == 1U,
"TC-NETSOCK-321d port, listener attribute and fd all survive: this is drop-the-connection-not-the-listener");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-321e registers REQ_DROP; the rebuild action lives only in net_listener_tick");
CHECK(s_net_socks[fd].readable == 0U && s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-321f session bookkeeping closes out: clear readable and move to CLOSED");
reset_all();
/* 非监听 client fd:无监听可保,同步排渣形态原样保留(N-17 只改监听型) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321g client-fd drop still succeeds");
CHECK(g_peers.listener_settle_calls == 0 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-321h client fd registers no settle and no REQ_DROP: nothing to rebuild");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 1U,
"TC-NETSOCK-321i client fd keeps the synchronous QUARANTINE close-out (bounded 600ms, legacy shape)");
reset_all();
}
/* TC-NETSOCK-322 close 前必先把挂起结算掉,再动芯片(顺序不可换) */
static void tc_322_close_before_chip(void)
{
int fd;
int close_at;
int settle_at;
char rxbuf[4];
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
/* 先登记一条挂起 recv:走 netTask 直调版 recv(无数据 + 阻塞 → 登记挂起) */
g_ns_pump = NULL; /* 本场景不靠泵,直接看登记结果 */
CHECK(net_recv_sock(&s_net_socks[fd], rxbuf, (int)sizeof(rxbuf), 0)
== NET_PENDING_REGISTERED,
"TC-NETSOCK-322p precondition: blocking recv registered a pending request (returns the sentinel)");
ch395f_fake_clear_log();
settle_at = g_peers.pending_settle_calls;
close_at = g_chip.close_calls;
CHECK(net_close_sock(&s_net_socks[fd]) == 0, "TC-NETSOCK-322a netTask-direct close succeeds");
CHECK(g_peers.pending_settle_calls == settle_at + 1,
"TC-NETSOCK-322b close must first settle this fd pending recv");
CHECK(g_chip.close_calls > close_at || g_fxchip.rst_close_calls > 0,
"TC-NETSOCK-322c the chip only sees CLOSE after the settlement");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-322d settlement always notifies (cancel notifies too, else the waiter hangs forever)");
reset_all();
}
static void ns_run_s4(void)
{
tc_300_poll_gates();
tc_301_poll_only_gated_sockets();
tc_302_poll_event_dispatch();
tc_303_phy_feed();
tc_310_listener_armed();
tc_311_can_read();
tc_320_close_two_paths();
tc_321_drop_conn_settle();
tc_322_close_before_chip();
}