diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index a40828ee..c1817d10 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -39,7 +39,6 @@ #define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试数据 */ #define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */ #define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms) */ -#define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI事务开始延时(ms) */ #define CH395F_SPI_BYTE_TIMEOUT_MS 100U /* E-34:字节级SPI有限超时(原 HAL_MAX_DELAY) */ /* DMA缓冲大小使用ch395f.h中公开的CH395F_SPI_DMA_BUF_SIZE */ #define CH395F_DMA_BUF_SIZE CH395F_SPI_DMA_BUF_SIZE @@ -694,7 +693,7 @@ ch395f_status_t ch395f_init(void) { } /* - * 函数功能:设置CH395的IP地址(低字节在前) + * 函数功能:设置CH395的IP地址(点分十进制序:p_ip[0]=第一段,见头文件声明注) * 入口参数:p_ip - 4字节IP地址指针 uint8_t* 192.168.x.x格式 * 限定条件:1. p_ip不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1:SET_IP 先于 INIT_CH395) @@ -723,7 +722,7 @@ void ch395f_set_ip_addr(uint8_t *p_ip) { } /* - * 函数功能:设置CH395的网关IP地址(低字节在前) + * 函数功能:设置CH395的网关IP地址(点分十进制序,同 set_ip_addr) * 入口参数:p_gwip - 4字节网关IP指针 uint8_t* * 限定条件:1. p_gwip不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1:SET_GWIP 先于 INIT_CH395) @@ -752,7 +751,7 @@ void ch395f_set_gwip_addr(uint8_t *p_gwip) { } /* - * 函数功能:设置CH395的子网掩码(低字节在前) + * 函数功能:设置CH395的子网掩码(点分十进制序,同 set_ip_addr) * 入口参数:p_mask - 4字节子网掩码指针 uint8_t* * 限定条件:1. p_mask不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1:SET_MASK 先于 INIT_CH395) diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h index a3a7a75f..a39aa49d 100644 --- a/Drivers/BSP/CH395F/ch395f.h +++ b/Drivers/BSP/CH395F/ch395f.h @@ -28,12 +28,10 @@ extern "C" { /* 无输入数据,无输出数据 */ #define CH395F_CMD_RESET_ALL 0x05U -#define CH395F_CMD_ENTER_SLEEP 0x03U /* 无输入数据,1 字节输出 */ #define CH395F_CMD_GET_IC_VER 0x01U #define CH395F_CMD_GET_CMD_STATUS 0x2CU -#define CH395F_CMD_GET_GLOB_INT_STATUS 0x29U #define CH395F_CMD_GET_PHY_STATUS 0x26U #define CH395F_CMD_GET_DHCP_STATUS 0x42U @@ -143,9 +141,11 @@ extern "C" { /* * SPI DMA 事务限制 * CH395F_SPI_DMA_MAX_PAYLOAD: 单次 DMA 事务最大有效载荷(总缓冲 - 4 字节命令头) - * 接收方向需一次性读空整个 Socket 接收缓冲(= 接收缓冲大小,避免 Trap 13 部分读 - * 导致剩余数据不可见),故总缓冲 = 接收缓冲大小 + 4(4 为命令头开销), - * 有效载荷 = 接收缓冲大小(2KB)。 + * 缓冲按"一条命令读空整个 Socket 接收缓冲(2KB)"定容——排空契约(Trap 25, + * bit3=1 残数据由 MCU 读出)的形态本身就是整缓冲单读。 + * (历史注记:旧注释把定容理由归给"Trap 13 部分读致剩余数据不可见",该机理 + * 2026-10-11 复核**未证实**且与手册 §5.34 相左,已降级为未证实历史观察, + * 见 Trap 记录 Trap 13 复核注;定容不依赖该观察。) * 2026-09-22:与 MSS 解耦(此前 = 2×MSS+4,MSS 下调会把 DMA 缓冲缩到装不下整缓冲 * → 整缓冲读会被截断丢弃)。 */ @@ -160,11 +160,9 @@ extern "C" { #define CH395F_CMD_SET_KEEPALIVE_INTVL 0x57U #define CH395F_CMD_SET_KEEPALIVE_CNT 0x58U #define CH395F_CMD_SET_KEEPALIVE_SN 0x59U -#define CH395F_CMD_EEPROM_ERASE 0xE9U -#define CH395F_CMD_EEPROM_WRITE 0xEAU -#define CH395F_CMD_EEPROM_READ 0xEBU -#define CH395F_CMD_READ_GPIO_REG 0xECH -#define CH395F_CMD_WRITE_GPIO_REG 0xEDU +/* N34 阶段0-9:EEPROM(0xE9..0xEB)/GPIO(0xEC/0xED)/SLEEP(0x03) 命令宏与 + * GET_GLOB_INT_STATUS(0x29,只有 2 字节版 0x19 有实现) 死声明已删除—— + * 全仓零引用核实于 2026-10-13;需要时按手册 §5.x 重新登记 */ /* * CH395 命令执行状态码(CMD_GET_CMD_STATUS 返回值,与芯片手册一致) @@ -199,10 +197,13 @@ extern "C" { #define CH395F_PHY_AUTO_NEGOTIATE 0x20U /* 自动协商 */ /* - * 功能参数标志位(CMD_SET_FUN_PARA) + * 功能参数标志位(CMD_SET_FUN_PARA,§5.52 表) + * bit3 SOCKET_CLOSE 需芯片版本 ≥0x46(版本门控见 net_init,N34 阶段0-6); + * bit1 TCP_SERVER 多连接模式——本项目单连接模型不用(红线,勿置 1); + * bit0 保留必须为 0(Trap 18 定案更正:0x09→0x08) */ #define CH395F_FUN_PARA_TCP_SERVER (1UL << 1) /* TCP Server 多连接模式 */ -#define CH395F_FUN_PARA_SOCKET_CLOSE (1UL << 3) /* Socket 关闭控制(0=芯片自动,1=外部控制) */ +#define CH395F_FUN_PARA_SOCKET_CLOSE (1UL << 3) /* Socket 关闭控制(0=芯片自动,1=外部控制;Trap 25 排空契约) */ #define CH395F_FUN_PARA_DISABLE_SEND_OK (1UL << 4) /* 禁用 SEND_OK 中断 */ /* @@ -235,16 +236,15 @@ extern "C" { #define CH395F_TCP_TIME_WAIT 0x0AU /* - * 全局中断状态位(CMD_GET_GLOB_INT_STATUS 1字节版,仅Socket 0~3) + * 全局中断状态位低字节(bit0~3 全局事件;§5.16——2 字节版 + * GET_GLOB_INT_STATUS_ALL 低字节同语义。Socket 事件位(bit4 起)由 + * net_poll 按 1U<<(i+4) 计算,旧 SOCK0..3 单列宏已删(N34 阶段0-5, + * 全仓零引用;1 字节版命令 0x29 亦已随死声明清理) */ #define CH395F_GINT_STAT_UNREACH 0x01U #define CH395F_GINT_STAT_IP_CONFLI 0x02U #define CH395F_GINT_STAT_PHY_CHANGE 0x04U #define CH395F_GINT_STAT_DHCP 0x08U -#define CH395F_GINT_STAT_SOCK0 0x10U -#define CH395F_GINT_STAT_SOCK1 0x20U -#define CH395F_GINT_STAT_SOCK2 0x40U -#define CH395F_GINT_STAT_SOCK3 0x80U /* * 全局中断状态位(CMD_GET_GLOB_INT_STATUS_ALL 2字节版,支持Socket 0~7) @@ -377,21 +377,23 @@ ch395f_status_t ch395f_init(void); */ /* - * 函数功能:设置 CH395 IP 地址(低字节在前) + * 函数功能:设置 CH395 IP 地址(点分十进制序:p_ip[0]=第一段,如 192; + * N34 阶段0-7 更正旧注"低字节在前"——调用方 parse_ip_string 产出 + * 点分数组直传、芯片按首段在前接受,台架多年 IP 生效实证) * 入口参数:p_ip - 4 字节 IP 地址指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ void ch395f_set_ip_addr(uint8_t *p_ip); /* - * 函数功能:设置网关 IP 地址(低字节在前) + * 函数功能:设置网关 IP 地址(点分十进制序,同 ch395f_set_ip_addr) * 入口参数:p_gwip - 4 字节网关 IP 指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ void ch395f_set_gwip_addr(uint8_t *p_gwip); /* - * 函数功能:设置子网掩码(低字节在前) + * 函数功能:设置子网掩码(点分十进制序,同 ch395f_set_ip_addr) * 入口参数:p_mask - 4 字节子网掩码指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ @@ -480,6 +482,8 @@ void ch395f_set_proto_type(uint8_t sock, uint8_t proto); * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * p_ip - 4 字节目标 IP 指针 uint8_t* * 限定条件:指针非空 + * 函数说明:字节序与 ch395f_get_remot_ipp 输出一致(net_connect 直传其输出, + * 台架出站连接往返实证;N34 阶段0-7 补注) */ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip); diff --git a/Drivers/BSP/NET/net_listener.c b/Drivers/BSP/NET/net_listener.c index 46e98389..0dbfd900 100644 --- a/Drivers/BSP/NET/net_listener.c +++ b/Drivers/BSP/NET/net_listener.c @@ -230,6 +230,10 @@ static int net_relisten_locked(int sockfd) { status = ch395f_tcp_listen((uint8_t)sockfd); /* LISTEN 失败 → BUSY(同样交重试) */ if (status != CH395F_ERR_SUCCESS) { + /* 0-3(N34 阶段0,U12 对称):open 已成而 LISTEN 失败——不 close 会把 + * 芯片 Socket 卡在"OPEN 无监听",下一轮 open 返 USE 又进失败分支 + * (U12 同源事故形态)。close 交重试轮,与 open 失败分支同形 */ + ch395f_close_socket((uint8_t)sockfd); /* 置 BUSY(结果回传调用方) */ return NET_ERR_RETURN_1(NET_ERR_BUSY); } @@ -394,7 +398,8 @@ int net_listener_try(int sockfd) { * 函数说明:1. 到点重试待办的重监听;成功即报告探针样本 * 2. 本 fd 有在途挂起 recv 时跳过(net_pending_active,L3 保险: * 重开 Socket 会摧毁在途连接) - * 3. 超过 NET_RELISTEN_DEADLINE_MS 放弃并记错误日志(防监听器永久失效) + * 3. 超过 NET_RELISTEN_DEADLINE_MS 停止 D10 快速重试并留痕 + * (监听不永久失效——pump 100ms 周期仍按"芯片不在听"认领重建) */ void net_listener_retry_tick(void) { /* 当前 tick */ @@ -407,6 +412,20 @@ void net_listener_retry_tick(void) { if (s_relisten_wait[i] == 0U) { continue; } + /* 0-4(N34 阶段0)状态门:仅本层仍 LISTENING/CLOSED 才重试。若已转 + * ESTABLISHED(新会话抢先到达),在途重试登记即陈旧——继续跑会 + * open→close 摧毁活连接(认领口径与 pump 一致,见 net_listener_pump) */ + { + /* 取控制块 */ + const net_sock_t *p_sock = net_get_sock(i); + if ((p_sock == NULL) || + ((p_sock->state != NET_SOCK_STATE_LISTENING) && + (p_sock->state != NET_SOCK_STATE_CLOSED))) { + /* 陈旧登记:清重试状态让位 */ + net_listener_clear(i); + continue; + } + } /* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(L3 保险) */ if (net_pending_active(i) != 0U) { continue; @@ -419,8 +438,9 @@ void net_listener_retry_tick(void) { if ((int32_t)(now - s_relisten_dead[i]) >= 0) { /* 清重试状态 */ net_listener_clear(i); - /* 放弃并记错误日志 */ - DBG_ERROR("sock%d relisten give up (Trap 09 not cleared)", i); + /* D10 快速重试预算耗尽留痕:后续交 pump 周期接管(N-32 单一重建 + * 入口),监听不会永久失效——旧文案"give up"夸大了失效语义 */ + DBG_ERROR("sock%d relisten retry budget exhausted, pump takes over", i); continue; } /* 重试成功 */ diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index ea0ef756..23e337fd 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -254,9 +254,21 @@ static bool s_mac_is_zero(const uint8_t *p_mac) { /* 复位芯片 */ ch395f_reset(); - /* 芯片版本留痕:SOCKET_CLOSE 需 ≥0x46、TCP_SERVER 需 ≥0x44(§5.52 表), - * 版本门槛排查曾是盲区(Trap 25 定案过程确认本芯片 SOCKET_CLOSE 实际生效) */ - DBG_INFO("ch395f ver=0x%02X fun_para=0x08", ch395f_get_version()); + /* 芯片版本留痕 + 0-6 版本门控(N34 阶段0,§5.52 表):bit3 SOCKET_CLOSE + * 需芯片 ≥0x46、TCP_SERVER 需 ≥0x44。本台架芯片实测 ≥0x46(Trap 25 定案 + * SOCKET_CLOSE 实际生效);门控防批次漂移——版本不足则**拒置 bit3** + * (退回芯片自动关断语义)并 [ERR] 留痕,不静默带病运行 */ + uint8_t chip_ver = ch395f_get_version(); + /* FUN_PARA 实际下发值(版本不足 = 0,bit3 拒置) */ + uint8_t fun_para = 0x00U; + if (chip_ver >= 0x46U) { + fun_para = CH395F_FUN_PARA_SOCKET_CLOSE; + } else { + DBG_ERROR("ch395f ver=0x%02X < 0x46: SOCKET_CLOSE(bit3) unsupported, cleared", + chip_ver); + } + /* 版本 + 实际生效 FUN_PARA 留痕(旧日志硬编码 0x08 改为实际值) */ + DBG_INFO("ch395f ver=0x%02X fun_para=0x%02X", chip_ver, fun_para); /* 功能参数(§5.52 表):bit3=1 SOCKET_CLOSE——DISCONNECT/TIMEOUT 后芯片 * 不清缓冲,由 MCU 读出残留数据后再关 Socket(Trap 25 三次定案的排空契约); @@ -267,7 +279,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) { * V1C/V1E/V2.2/V2.3 的 bit0 全是保留位,官方例程 bit0=0 下 Socket 0~7 照常 * 可用、FTP 例程本身就传 0x08;证据链见 Trap 18「定案更正」, * 0x08 下的多 Socket 台架复测登记 N-31) */ - ch395f_set_fun_para(0x08); + ch395f_set_fun_para(fun_para); /* 设置 TCP MSS:维持 1024(512 裕量方案未采纳,当年支撑它的台架判据不可信, * 已随 TC-NET-604 结案为非缺陷一并撤回(2026-09-23,见 Trap 21)) */ @@ -1110,6 +1122,10 @@ int net_listen_locked(int sockfd) { status = ch395f_tcp_listen(sockfd); /* LISTEN 失败 → BUSY */ if (status != CH395F_ERR_SUCCESS) { + /* 0-3(N34 阶段0,U12 对称):open 已成而 LISTEN 失败必须 close, + * 否则芯片 Socket 卡"OPEN 无监听"、后续 open 恒返 USE(同 + * net_relisten_locked 对应分支) */ + ch395f_close_socket(sockfd); return NET_ERR_RETURN_1(NET_ERR_BUSY); } @@ -1905,7 +1921,8 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len); /* 累计接收字节统计 */ p_sock->recv_bytes += (uint32_t)read_len; - /* 已消费数据(注:部分读受 Trap 13 限制,下一次 RECV_OK 会重新置位) */ + /* 已消费数据(部分读安全:手册 §5.34 未读数据保留可续读,旧"Trap 13 + * 部分读归零"已降级为未证实历史观察;下一次 RECV_OK 重新置 readable) */ p_sock->readable = 0; /* 返回本次读出字节数 */ return read_len; @@ -1966,7 +1983,8 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, * ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径零额外 SPI 事务 * (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位) * ② 到点(与状态无关,旧自旋亦在任何状态下检查超时)→ NET_ERR_TIMEDOUT - * ③ CLOSED:先排空(Trap 13:FIN 可能先于数据)→ 有数据按数据完成,否则 EOF(0) + 自动重监听 + * ③ CLOSED:先排空(bit3=1 芯片断连后残数据留缓冲待 MCU 读——Trap 25 排空契约; + * 旧注归 Trap 13 已随其复核注降级)→ 有数据按数据完成,否则 EOF(0) + 自动重监听 * 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED 是合法路径; * 陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀正在建链的等待 * 看门狗兜底:到点后仍未了结 = 完成逻辑缺位 → 强制了结留痕 @@ -3386,7 +3404,7 @@ static void handle_disconnect_event(net_sock_t *p_sock) { /* Trap 22 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡; * 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现); * N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交 - * net_listener_pump(每 50 ms 回读芯片状态,监听失位即重建)。原处直接 + * net_listener_pump(每 100 ms 回读芯片状态,监听失位即重建)。原处直接 * relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先" * 于消费者 recv(P1 已消除其后果,P2 消除其可能) */ /* 回读缓冲(芯片状态 2 字节) */ @@ -3499,11 +3517,12 @@ static void handle_disconnect_event(net_sock_t *p_sock) { * 返回值:无 * 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文 * 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层 - * 2. 不在此读取 recv_len,避免与 net_recv_locked 中的读取冲突(Trap 13) + * 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI + * 查询;Trap 13 时代的"读取冲突"顾虑已随复核注降级,结构保留) */ static void handle_recv_event(net_sock_t *p_sock) { /* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len, - * 避免和 net_recv_locked 里面的 ch395f_get_recv_len 冲突(Trap 13) */ + * 单一读取点在 net_recv_locked(省 SPI;原 Trap 13 顾虑已降级) */ /* 置可读标志,供 net_select 在无 SPI 访问下判读 */ p_sock->readable = 1; /* 触发数据到达事件回调 */ diff --git a/test/board/ch395f_test_task.c b/test/board/ch395f_test_task.c index e43d6d85..af8af638 100644 --- a/test/board/ch395f_test_task.c +++ b/test/board/ch395f_test_task.c @@ -351,8 +351,8 @@ static void phase2_tc201(void) * 【掩码 (1U << (sock + 4)) 的由来】 * 全局状态的 bit0~3 被 4 个全局事件占用,因此 Socket N 的事件标志 * 固定从 bit4 开始排列 —— Socket N 对应第 (N + 4) 位: - * sock=0 → 1<<4 = 0x0010(CH395F_GINT_STAT_SOCK0) - * sock=1 → 1<<5 = 0x0020(CH395F_GINT_STAT_SOCK1) + * sock=0 → 1<<4 = 0x0010(Socket0 事件位,宏已删 N34-0-5) + * sock=1 → 1<<5 = 0x0020(Socket1 事件位,宏已删 N34-0-5) * ... * sock=7 → 1<<11 = 0x0800(CH395F_GINT_STAT_SOCK7) * `int_st & (1U << (sock + 4))` 非 0