/* * 模块名称:假 CH395F 芯片(NET 域主机侧单测共享件) * 模块功能:给 NET 域的主机侧单测一个**可脚本化的假芯片**。真实 ch395f.h 拉 HAL 与 * SPI 寄存器定义,PC 上编不动;而 net_listener / net_pending / net_events * 的行为恰恰完全由"芯片此刻报什么状态"决定,必须有假件才能测。 * * ⚠ 仅供 test/host/** 使用,不参与 MCU 构建。 * * 为什么值得共享一份(而不是每模块自带桩):三份假芯片各自漂移时,"假件与被测代码 * 同时错"这类问题再也不会报出来。各模块的 stub/ch395f.h 只做一件事——include 本文件, * 于是签名与常量只有一份定义。 * * 常量与类型必须与 Drivers/BSP/CH395F/ch395f.h 逐字一致(2026-10-23 从真实头抄录): * CH395F_ERR_SUCCESS 0x00 / CH395F_PROTO_TYPE_TCP 0x03 / CH395F_SOCKET_CLOSED 0x00 * CH395F_TCP_CLOSED 0x00 / CH395F_TCP_LISTEN 0x01;ch395f_sock_state_t 为 * { uint8_t open; uint8_t tcp_state; } 两字节。 * * 必须复刻的两条芯片怪癖(不复刻就等于把被测代码的坑假件掉): * 1. ch395f_get_sock_int_status() 是**整体读即清**、芯片没有按位清除手段。本假件 * 返回该 socket 的挂起位图并同步清零。AGENTS 模块约定里 B3 的实证坑("顺手 * 全量清一遍"把别的在用 Socket 的门焊死)靠这条才照得出来。 * 2. *_cached 系列返回的是**缓存值**而不是现场值:open 状态与 TCP 状态分开、且 * "监听还活着"的判据是 (open != CLOSED && tcp_state == LISTEN)。测试据此能构造 * "缓存陈旧"的形态。 * * 脚本化模型(沿用本仓既有假件风格:nand_port 的真头+假 diskio、lftpd_io 的可注入假堆): * - 每个读函数一条按调用序号取的返回队列,队列耗尽后回落到**最后一个值**(便于 * 写"一直可读""一直关不掉"这类持续状态); * - 每个写/控制函数一条调用记录(次数 + 最近一次入参),另给逐函数调用计数, * 让"这一轮根本不该碰芯片"成为可断言的事; * - g_chip.log[] 记录**调用顺序**(短名),用于断言"先清接收、再关、再读即清"这类 * 时序契约; * - ch395f_fake_reset() 一次全清。 * * 创建日期:2026-10-23 */ #ifndef CH395F_FAKE_H #define CH395F_FAKE_H #include /* —— 与真实 ch395f.h 逐字一致的常量(值抄自真实头,勿"顺手改")—— */ #define CH395F_ERR_SUCCESS 0x00U #define CH395F_PROTO_TYPE_TCP 0x03U #define CH395F_SOCKET_CLOSED 0x00U #define CH395F_SOCKET_OPEN 0x05U /* 真头 ch395f.h:220 = 0x05U(05H SOCKET_OPEN,docs/芯片手册/CH395F.md:527)*/ #define CH395F_TCP_CLOSED 0x00U #define CH395F_TCP_LISTEN 0x01U #define CH395F_TCP_ESTABLISHED 0x04U /* 真头 ch395f.h:229 = 0x04U(04H TCP_ESTABLISHED,docs/芯片手册/CH395F.md:539)*/ /* 发送/接收缓冲尺寸宏链:**与真实 ch395f.h 同构转抄**(2026-10-23 逐条核对)。 * net_pending.h 里的 NET_SEND_CHUNK_MAX 由这两个宏推导,是"一次最多往芯片写多少" * 的真实上界,假件不能自己发明数字(否则分块测试就是在测假件的常量): * RAM_BLOCK_SIZE 512 / SEND_BLOCKS 2 → SEND_BUF 1024 * RECV_BLOCKS 4 → RECV_BUF 2048 → SPI_DMA_BUF 2052 → SPI_DMA_MAX_PAYLOAD 2048 * ⇒ NET_SEND_CHUNK_MAX = min(1024, 2048) = 1024 */ #define CH395F_RAM_BLOCK_SIZE 512U #define CH395F_SEND_BLOCKS 2U #define CH395F_SEND_BUF_SIZE_BYTES (CH395F_SEND_BLOCKS * CH395F_RAM_BLOCK_SIZE) #define CH395F_RECV_BLOCKS 4U #define CH395F_RECV_BUF_SIZE_BYTES (CH395F_RECV_BLOCKS * CH395F_RAM_BLOCK_SIZE) #define CH395F_SPI_DMA_BUF_SIZE (CH395F_RECV_BUF_SIZE_BYTES + 4U) #define CH395F_SPI_DMA_MAX_PAYLOAD (CH395F_SPI_DMA_BUF_SIZE - 4U) #define CH395F_FAKE_SOCK_MAX 8 /* 与 NET_MAX_SOCKETS 同量级,测试自用 */ #define CH395F_FAKE_QUEUE_LEN 24 #define CH395F_FAKE_LOG_LEN 96 #define CH395F_FAKE_GLOB_QUEUE 24 /* 写内容捕获容量(测试自用,真实头无此宏 → 不参与假芯片常量对齐门): * 256 够覆盖"一个小数据报全文"与"分块发送首块的前 256 字节"两类断言。 */ #define CH395F_FAKE_WRITE_CAP 256 /* ===== 扩展区(2026-10-24,net_socket 主机单测批次;**纯新增**,上面既有宏/字段/函数 * 一字未动,net_listener / net_events / net_pending 三套的语义不受影响)===== * * 追加的是 net_socket.c 这一层会调、而前三个模块不调的芯片原语(初始化下发类 + * 全局中断 + sock_reset 复位机 + cmd_done 判据)。签名/常量同样**逐字抄自真实头** * Drivers/BSP/CH395F/ch395f.h,改真头即编译期/断言期暴露漂移。 * * 必须复刻的第三条芯片怪癖(前两套测不到、net_socket.c 却是主路): * 3. GET_GLOB_INT_STATUS_ALL(0x19,2 字节)**整体读即清**,且它是全局寄存器 * (含全部 Socket 事件位)。AGENTS 模块约定 B3:唯一读者只能是 net_poll。 * 假件按调用序号返回预置值,并单独计数,让"别处偷偷读全局"成为可断言的事。 * 4. sock_reset 复位机(E4 定案四步:读空 RX → CLOSE → 排空事件(quiescent) → * 等 byte0=00H,含 LISTEN 态二段 CLOSE 补发)。这里做**契约等价**实现: * 阶段推进与最短等待(GRACEFUL 100ms / QUARANTINE 400ms)照抄驱动, * 但不复制驱动的超时数值细节(那属于驱动自己的单测面)。 */ #define CH395F_ERR_BUSY 0x10U /* 真头 ch395f.h:170 */ #define CH395F_ERR_MEM 0x11U /* 真头 ch395f.h:171 */ #define CH395F_ERR_TIMEOUT 0x13U /* 真头 ch395f.h:173 */ #define CH395F_ERR_VAL 0x19U /* 真头 ch395f.h:179 */ #define CH395F_ERR_USE 0x1BU /* 真头 ch395f.h:181(open 已被占用)*/ #define CH395F_PROTO_TYPE_UDP 0x02U /* 真头 ch395f.h:213 */ #define CH395F_FUN_PARA_SOCKET_CLOSE (1UL << 3) /* 真头 ch395f.h:205 */ #define CH395F_TCP_MSS 1024U /* 真头 ch395f.h:124 */ #define CH395F_PHY_DISCONN 0x01U /* 真头 ch395f.h:191 */ #define CH395F_GINT_STAT_PHY_CHANGE 0x04U /* 真头 ch395f.h:245 */ #define CH395F_SOCK_STATE_NONE 0xFFU /* 真头 ch395f.h:762 */ #define CH395F_SINT_STAT_SENBUF_FREE 0x01U /* 真头 ch395f.h:263 */ #define CH395F_SINT_STAT_SEND_OK 0x02U /* 真头 ch395f.h:264 */ #define CH395F_SINT_STAT_RECV_OK 0x04U /* 真头 ch395f.h:265 */ #define CH395F_SINT_STAT_CONNECT 0x08U /* 真头 ch395f.h:266 */ #define CH395F_SINT_STAT_DISCONNECT 0x10U /* 真头 ch395f.h:267 */ #define CH395F_SINT_STAT_SOCK_TIMEOUT 0x40U /* 真头 ch395f.h:269 */ typedef enum { CH395F_STATUS_OK = 0, /* 真头 ch395f.h:275 */ CH395F_STATUS_ERROR = -1, CH395F_STATUS_TIMEOUT = -2, CH395F_STATUS_NOT_DETECTED = -3 } ch395f_status_t; typedef enum { CH395F_RESET_GRACEFUL = 0, /* 真头 :803:最短等待 100ms */ CH395F_RESET_QUARANTINE /* 真头 :804:最短等待 400ms */ } ch395f_reset_mode_t; typedef enum { CH395F_DONE_OPEN = 0, /* 真头 :826:byte0 变 05H */ CH395F_DONE_LISTEN, /* 真头 :827:byte0=05H 且 TCP=01H */ CH395F_DONE_CLOSE /* 真头 :828:byte0=00H */ } ch395f_done_what_t; /* 复位机阶段(与驱动同名状态的**契约等价**简化,不代表驱动内部实现) */ #define CH395F_FAKE_RST_IDLE 0 #define CH395F_FAKE_RST_DRAINED 1 /* 已读空 RX 并发过 CLOSE */ #define CH395F_FAKE_RST_MINWAIT 2 /* 等最短沉降窗(100/400ms) */ #define CH395F_FAKE_RST_QUIET 3 /* 排空本 Socket 事件(连续 2 轮无位) */ #define CH395F_FAKE_RST_WAITCLS 4 /* 等 byte0=00H(含二段 CLOSE 补发) */ #define CH395F_FAKE_RST_QUIESCENT 2U typedef struct { uint8_t open; uint8_t tcp_state; } ch395f_sock_state_t; /* —— 可预置的芯片侧世界 —— */ typedef struct { uint8_t cached_open[CH395F_FAKE_SOCK_MAX]; /* sock_state_cached().open */ uint8_t cached_tcp[CH395F_FAKE_SOCK_MAX]; /* sock_state_cached().tcp_state */ uint8_t status_byte0[CH395F_FAKE_SOCK_MAX]; /* get_socket_status 的 sock 状态 */ uint8_t status_byte1[CH395F_FAKE_SOCK_MAX]; /* get_socket_status 的 TCP 状态 */ uint8_t int_status[CH395F_FAKE_SOCK_MAX]; /* 读即清的挂起位图 */ uint16_t recv_len_q[CH395F_FAKE_SOCK_MAX][CH395F_FAKE_QUEUE_LEN]; int recv_len_n[CH395F_FAKE_SOCK_MAX]; int recv_len_i[CH395F_FAKE_SOCK_MAX]; uint8_t remote[CH395F_FAKE_SOCK_MAX][6]; /* get_remot_ipp 的 6 字节:ip[4]+port(小端) */ uint16_t link_change; /* link_change_count(链路变更计数) */ uint8_t link_up; /* link_up_cached */ uint8_t open_rc[CH395F_FAKE_SOCK_MAX]; /* open_socket 返回码 */ uint8_t listen_rc[CH395F_FAKE_SOCK_MAX]; /* tcp_listen 返回码 */ } ch395f_fake_world_t; typedef struct { /* 调用计数("不该碰芯片"的断言面) */ int open_calls; int close_calls; int listen_calls; int int_status_calls; int get_status_calls; int read_calls; int write_calls; int set_proto_calls; int set_port_calls; int cached_calls; /* 最近一次入参 */ uint8_t last_sock; uint16_t last_read_len; uint16_t last_port; uint8_t last_proto; uint8_t last_keepalive; int keepalive_calls; /* 调用顺序日志(短名首字母): * 'c'=close 'i'=int_status(读即清) 's'=get_socket_status 'r'=read_recv_buf * 'R'=get_recv_len 'o'=open_socket 'l'=tcp_listen 'p'=set_sour_port * 'P'=set_proto_type 'w'=write_send_buf * 扩展区新增(net_socket 批次):'G'=读全局中断 'v'=sock_events(读即清) * 'X'=sock_reset_begin 'x'=sock_reset_poll 'z'=cmd_done 'T'=tcp_connect * 'I'=set_ip_addr 'N'=set_gwip 'K'=set_mask 'M'=set_mac 'D'=set_dhcp * 'F'=set_fun_para 'Q'=set_tcp_mss 'b'=set_send_buf 'u'=set_recv_buf * 'e'=set_des_ip 'q'=set_des_port 'g'=get_phy_status 'E'=check_exist * 'Z'=reset 'n'=init */ char log[CH395F_FAKE_LOG_LEN]; int log_n; } ch395f_fake_obs_t; extern ch395f_fake_world_t g_fake; extern ch395f_fake_obs_t g_chip; void ch395f_fake_reset(void); /* 全清并把所有 socket 设为"已关闭" */ void ch395f_fake_push_recv_len(uint8_t sock, uint16_t v); void ch395f_fake_set_listening(uint8_t sock); void ch395f_fake_set_peer(uint8_t sock, uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint16_t peer_port); /* 预置对端六元组 */ /* 缓存:open!=CLOSED 且 tcp=LISTEN */ /* —— 假芯片实现(签名与真实 ch395f.h 一字不改)—— */ ch395f_sock_state_t ch395f_sock_state_cached(uint8_t sn); ch395f_sock_state_t ch395f_get_sock_state(uint8_t sn); void ch395f_get_socket_status(uint8_t sock, uint8_t *status); uint8_t ch395f_get_sock_int_status(uint8_t sock); uint16_t ch395f_get_recv_len(uint8_t sock); void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len); void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len); uint8_t ch395f_open_socket(uint8_t sock); uint8_t ch395f_close_socket(uint8_t sock); uint8_t ch395f_tcp_listen(uint8_t sock); void ch395f_set_proto_type(uint8_t sock, uint8_t proto); void ch395f_set_sour_port(uint8_t sock, uint16_t port); void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf); void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable); uint8_t ch395f_link_up_cached(void); uint16_t ch395f_link_change_count(void); /* ===== 扩展区第二部分:新增状态世界 + 新增假芯片原型 ===== * ⚠ 刻意**不往既有 ch395f_fake_world_t / ch395f_fake_obs_t 里加字段**,而是另开一份 * ch395f_fake_ext_t / ch395f_fake_ext_obs_t:既有三套单测的复位与断言面一字不动, * 新增状态由 ch395f_fake_reset() 调 ch395f_fake_ext_reset() 一并清零(场景间不残留)。 */ typedef struct { /* —— 初始化/参数下发面(net_init 主路)—— */ int exist_rc; /* ch395f_check_exist 返回(ch395f_status_t)*/ int reset_rc; /* ch395f_reset 返回 */ int init_rc; /* ch395f_init 返回 */ uint8_t version; /* ch395f_get_version(版本门控 0x46 的用点位)*/ uint8_t dhcp_enable; /* ch395f_set_dhcp */ uint8_t ping_enable; /* ch395f_ping_enable */ uint8_t ip[4]; /* ch395f_set_ip_addr */ uint8_t gw[4]; /* ch395f_set_gwip_addr */ uint8_t mask[4]; /* ch395f_set_mask_addr */ uint8_t mac[6]; /* ch395f_set_mac_addr */ uint32_t fun_para; /* ch395f_set_fun_para 最近一次实发值 */ uint16_t mss; /* ch395f_set_tcp_mss */ uint8_t retrans_cnt; uint16_t retrans_period; uint32_t ka_idle; uint32_t ka_intvl; uint8_t ka_cnt; uint8_t send_blk_start[CH395F_FAKE_SOCK_MAX]; /* ch395f_set_send_buf */ uint8_t send_blk_cnt[CH395F_FAKE_SOCK_MAX]; uint8_t recv_blk_start[CH395F_FAKE_SOCK_MAX]; /* ch395f_set_recv_buf */ uint8_t recv_blk_cnt[CH395F_FAKE_SOCK_MAX]; uint8_t des_ip[CH395F_FAKE_SOCK_MAX][4]; /* ch395f_set_des_ip */ uint16_t des_port[CH395F_FAKE_SOCK_MAX]; /* ch395f_set_des_port */ uint8_t connect_rc[CH395F_FAKE_SOCK_MAX]; /* ch395f_tcp_connect 返回 */ /* —— 全局中断(0x19,整体读即清;唯一读者应为 net_poll)—— */ uint16_t glob_q[CH395F_FAKE_GLOB_QUEUE]; int glob_n; int glob_i; uint8_t phy_status; /* ch395f_get_phy_status(喂 glob_event_feed)*/ /* —— sock_reset 复位机(契约等价,见文件头注释 4)—— */ uint8_t rst_state[CH395F_FAKE_SOCK_MAX]; uint8_t rst_mode[CH395F_FAKE_SOCK_MAX]; uint32_t rst_t0[CH395F_FAKE_SOCK_MAX]; uint8_t rst_quiet[CH395F_FAKE_SOCK_MAX]; /* 连续无事件轮数 */ /* 1=复位机**不**替芯片把 cached_open 落回 CLOSED(造"永远关不掉"的芯片形态, * 用来量上层的复位超时收口)。默认 0 → 与既有行为一致。 */ uint8_t rst_keep_open[CH395F_FAKE_SOCK_MAX]; /* GET_RECV_BUF 的**脚本化内容**(默认长度 0 → 与既有"全零交付"逐字相同)。 * UDP 报文头(长度/源端口/源 IP)只有内容可脚本化才能被断言:net_recvfrom 要 * 从芯片头里拆出对端地址,全零内容下"拆对了"与"拆反了"看起来一模一样。 */ uint8_t recv_bytes[128]; uint16_t recv_bytes_n; /* SINT 的**电平型**来源(可脚本化):读即清之后立刻把这个掩码放回 int_status。 * 默认 0 → 与既有假件行为逐字相同(读一次就没了)。真实芯片的 RECV_OK / * SENBUF_FREE 本就是"条件位":只要条件还在(RX 仍非空 / 发送缓冲仍空),下一轮 * 就还会再报一次。net_socket.c 的多块发送要的就是这个稳态,没有它就只能 * 造"写完一块就再也等不到空闲"的假死形态。 */ uint8_t sint_sticky[CH395F_FAKE_SOCK_MAX]; /* —— 2026-11-03 UDP 队列化改造追加(默认全 0 → 既有行为逐字不变)—— */ /* GET_RECV_BUF 的**流式读**开关:UDP 一个数据报 = 8 字节信息头 + 载荷, * 生产代码是**两次**读(先剥头再读载荷),而 set_recv_bytes 每次都从预置 * 内容第 0 字节交付——那样"剥掉头"与"把包头当载荷"在断言里长得一样。 * 本开关让交付内容带游标:第二次读拿到的是第 8 字节往后的载荷。 */ uint8_t recv_stream_en; uint16_t recv_pos; /* 游标:已交付字节数(截断排空要断言它) */ /* WRITE_SEND_BUF 的**内容捕获**开关:假芯片原本忽略 p_data(既有用例只数次数), * 但 UDP 发送要钉"写进芯片的是不是调用方那份字节、分块边界对不对"。 * 默认 0 → 既有"内容不可断言"的行为一字不变。 */ uint8_t write_cap_en; uint8_t write_cap[CH395F_FAKE_WRITE_CAP]; /* 按写入顺序拼接的字节(够 2 块 1KB 的前 256B) */ uint16_t write_cap_n; /* 已捕获字节数(超出容量后只计数不存) */ uint32_t write_bytes; /* 芯片收到的总字节数(含未捕获部分) */ } ch395f_fake_ext_t; typedef struct { int exist_calls; int reset_calls; int init_calls; int set_ip_calls; int set_gw_calls; int set_mask_calls; int set_mac_calls; int set_dhcp_calls; int ping_calls; int set_fun_para_calls; int set_mss_calls; int set_retrans_calls; int set_ka_calls; int set_send_buf_calls; int set_recv_buf_calls; int set_des_ip_calls; int set_des_port_calls; int connect_calls; int glob_calls; /* 读全局中断次数(B3:除 net_poll 外应为 0)*/ int glob_bits; /* 最近一次读到的 16 位全局位图 */ int sock_events_calls; /* ch395f_sock_events(只碰本 Socket 的护栏原语)*/ int rst_begin_calls; int rst_begin_reject; /* begin 被拒次数(复位机已在运行)*/ int rst_poll_calls; int rst_done[CH395F_FAKE_SOCK_MAX]; /* 复位机完成计数 */ int rst_fail[CH395F_FAKE_SOCK_MAX]; /* 复位机失败计数 */ int cmd_done_calls; uint8_t last_cmd_done_what; int rst_close_calls; /* 复位机内部发出的 CLOSE 命令(含二段补发)*/ int rst_rx_discard_calls; /* 复位机 RX 排空事务次数 */ } ch395f_fake_ext_obs_t; extern ch395f_fake_ext_t g_fx; extern ch395f_fake_ext_obs_t g_fxchip; /* 由 ch395f_fake_reset() 自动调用;单独暴露是为了让测试能只清扩展面 */ void ch395f_fake_ext_reset(void); void ch395f_fake_push_glob(uint16_t v); /* 预置全局中断返回序列 */ void ch395f_fake_set_sint_sticky(uint8_t sock, uint8_t mask); /* 预置 GET_RECV_BUF 交付的字节(n=0 撤销预置);p 可为 NULL(表示填 n 个 0xA5) */ void ch395f_fake_set_recv_bytes(const uint8_t *p, uint16_t n); /* 同上并**开启流式读**(游标归零):UDP 用例要的是"头 8 字节 + 载荷"两次读 * 各自拿到正确片段,而不是每次都从第 0 字节重交。 */ void ch395f_fake_set_recv_stream(const uint8_t *p, uint16_t n); /* 开启 WRITE_SEND_BUF 内容捕获(g_fx.write_cap / write_cap_n / write_bytes): * 默认关 → 既有"内容不可断言"的行为一字不变。 */ void ch395f_fake_enable_write_capture(void); /* 顺序断言辅助:清空调用日志 / 找某个短名第一次出现的位置(-1=没调过)。 * 刻意提供"清零日志",否则一次长轮询(如 sock_reset 的几十轮 'x')会把 96 格 * 环形外的顺序信息挤掉,让后续场景的顺序断言失真。 */ void ch395f_fake_clear_log(void); int ch395f_fake_log_find(char c); /* —— 新增假芯片实现(签名与真实 ch395f.h 一字不改)—— */ ch395f_status_t ch395f_check_exist(void); ch395f_status_t ch395f_reset(void); ch395f_status_t ch395f_init(void); uint8_t ch395f_get_version(void); void ch395f_set_fun_para(uint32_t para); void ch395f_set_tcp_mss(uint16_t mss); void ch395f_set_retrans_count(uint8_t cnt); void ch395f_set_retrans_period(uint16_t period); void ch395f_set_keepalive_idle(uint32_t time); void ch395f_set_keepalive_intvl(uint32_t time); void ch395f_set_keepalive_cnt(uint8_t cnt); void ch395f_set_dhcp(uint8_t enable); void ch395f_ping_enable(uint8_t enable); void ch395f_set_ip_addr(uint8_t *p_ip); void ch395f_set_gwip_addr(uint8_t *p_gwip); void ch395f_set_mask_addr(uint8_t *p_mask); void ch395f_set_mac_addr(uint8_t *p_mac); void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count); void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count); void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip); void ch395f_set_des_port(uint8_t sock, uint16_t port); uint8_t ch395f_tcp_connect(uint8_t sock); uint8_t ch395f_get_phy_status(void); uint16_t ch395f_get_glob_int_status_all(void); void ch395f_glob_event_feed(uint16_t glob_bits); uint8_t ch395f_sock_events(uint8_t sn); int ch395f_sock_reset_begin(uint8_t sn, ch395f_reset_mode_t mode); int ch395f_sock_reset_poll(uint8_t sn); int ch395f_cmd_done(uint8_t sn, ch395f_done_what_t what); #endif /* CH395F_FAKE_H */