Files
STM32F4-Base/test/host/net_socket/ns_s4_state.c
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +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,
* 探针窗口心跳缺口修复起 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 heartbeat gap fix: 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 (relisten settle)");
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:无监听可保,同步排渣形态原样保留(心跳缺口修复只改监听型) */
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();
}