/* * 模块名称:CH395F Driver Test Suite * 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例, * 覆盖 SPI 命令路径、Socket 状态机、大文件传输、边界条件等 * 适用平台:STM32F4 系列(CH395F 以太网芯片) * 作者:王建锋 * 创建日期:2026-07-19 * 修改记录: * 2026-07-19 Phase 1/2/3 implemented * 2026-07-22 全面重构为单连接模式,增加 Phase 5/8/10 */ #include #include #include "ch395f_test.h" #include "ch395f.h" #include "net_socket.h" #include "net_select.h" #define DBG_TAG "[CH395T]" #include "dbg_log.h" test_stats_t g_test_stats; /* * Phase 2 配置默认值 */ #ifndef PHASE2_REMOTE_IP #define PHASE2_REMOTE_IP "192.168.1.2" #endif #ifndef PHASE2_REMOTE_PORT #define PHASE2_REMOTE_PORT 8081 #endif #ifndef PHASE2_LOCAL_PORT #define PHASE2_LOCAL_PORT 50000 #endif #ifndef PHASE2_LOOP_COUNT #define PHASE2_LOOP_COUNT 3 #endif #ifndef PHASE2_TIMEOUT_MS #define PHASE2_TIMEOUT_MS 10000 #endif /* * Phase 3 配置默认值 */ #ifndef PHASE3_REMOTE_IP #define PHASE3_REMOTE_IP "192.168.1.2" #endif #ifndef PHASE3_REMOTE_PORT #define PHASE3_REMOTE_PORT 8082 #endif #ifndef PHASE3_LOCAL_PORT #define PHASE3_LOCAL_PORT 60000 #endif #ifndef PHASE3_ECHO_COUNT #define PHASE3_ECHO_COUNT 30 #endif #ifndef PHASE3_TIMEOUT_MS #define PHASE3_TIMEOUT_MS 30000 #endif /* * Phase 5 配置默认值(大文件传输) */ #ifndef PHASE5_LOCAL_PORT #define PHASE5_LOCAL_PORT 60000 #endif #ifndef PHASE5_FILE_SIZE #define PHASE5_FILE_SIZE 20480 /* 20KB */ #endif #ifndef PHASE5_CHUNK_SIZE #define PHASE5_CHUNK_SIZE 4096 /* 单次 NET 层收发块大小 */ #endif #ifndef PHASE5_BUF_SIZE #define PHASE5_BUF_SIZE 65536 /* 接收缓冲最大 64KB */ #endif /* * Phase 8 配置默认值(边界条件) */ #ifndef PHASE8_LOCAL_PORT #define PHASE8_LOCAL_PORT 8080 #endif /* * Phase 10 配置默认值(KeepAlive) */ #ifndef PHASE10_KEEPALIVE_IDLE_MS #define PHASE10_KEEPALIVE_IDLE_MS 60000 /* 空闲 60 秒触发探测 */ #endif /* ============================ Phase 1 ============================ */ void ch395f_phase1_tests(void) { uint8_t i = 0; int ver = 0; DBG_INFO("=== CH395F Phase 1 Tests ==="); ver = ch395f_get_version(); TEST_CHECK(ver > 0, "GET_VERSION: 0x%02X (ver=%d)", ver, ver & 0x3F); uint8_t cmd_st = ch395f_get_cmd_status(); TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, "GET_CMD_STATUS: 0x%02X (%s)", cmd_st, cmd_st == CH395F_ERR_SUCCESS ? "SUCCESS" : cmd_st == CH395F_ERR_BUSY ? "BUSY" : "OTHER"); uint8_t ip_info[20]; ch395f_get_ip_inf(ip_info); DBG_INFO("GET_IP_INF:"); DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); DBG_INFO(" MASK: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); DBG_INFO(" DNS1: %d.%d.%d.%d", ip_info[12], ip_info[13], ip_info[14], ip_info[15]); DBG_INFO(" DNS2: %d.%d.%d.%d", ip_info[16], ip_info[17], ip_info[18], ip_info[19]); TEST_CHECK(ip_info[0]==192 && ip_info[1]==168 && ip_info[2]==1 && ip_info[3]==100, "IP match"); TEST_CHECK(ip_info[4]==192 && ip_info[5]==168 && ip_info[6]==1 && ip_info[7]==1, "GW match"); TEST_CHECK(ip_info[8]==255 && ip_info[9]==255 && ip_info[10]==255 && ip_info[11]==0, "MASK match"); uint16_t gint_all = ch395f_get_glob_int_status_all(); TEST_CHECK((gint_all & 0xFF) == 0x04 || (gint_all & 0xFF) == 0x00, "GINT_STATUS_ALL: 0x%04X", gint_all); uint8_t gint_1b = ch395f_get_glob_int_status(); TEST_CHECK(gint_1b == 0x00, "GINT_STATUS_1byte: 0x%02X (expect 0x00)", gint_1b); { uint8_t st[2]; ch395f_get_socket_status(0, st); TEST_CHECK(st[0] == 0x05 && st[1] == 0x01, "SOCK_STATUS(0): sock=0x%02X, tcp=0x%02X", st[0], st[1]); } ch395f_set_arp(10, 5); TEST_CHECK(1, "SET_ARP(period=10*100ms, cnt=5): OK"); ch395f_set_ttl(0, 64); TEST_CHECK(1, "SET_TTL(sock=0, ttl=64): OK"); ch395f_clear_recv_buf(0); TEST_CHECK(1, "CLEAR_RECV_BUF(sock=0): OK"); for (i = 0; i < 8; i++) { uint8_t si = ch395f_get_sock_int_status(i); TEST_CHECK(si == 0x00, "SOCK_INT(%d): 0x%02X", i, si); } TEST_REPORT("Phase 1"); } /* ============================ Phase 2 ============================ */ void ch395f_phase2_tests(void) { uint8_t sock = 0; uint8_t sock_int = 0; uint8_t rx_buf[300] = {0}; int i = 0; uint32_t tick = 0; uint16_t int_st = 0; int iter_pass = 0; DBG_INFO("=== CH395F Phase 2 Tests (TCP Client - Direct Driver) ==="); uint8_t ip_arr[4]; { uint32_t a[4]; sscanf(PHASE2_REMOTE_IP, "%lu.%lu.%lu.%lu", &a[0], &a[1], &a[2], &a[3]); ip_arr[0] = (uint8_t)a[0]; ip_arr[1] = (uint8_t)a[1]; ip_arr[2] = (uint8_t)a[2]; ip_arr[3] = (uint8_t)a[3]; } DBG_INFO("Target: %s:%d", PHASE2_REMOTE_IP, PHASE2_REMOTE_PORT); sock = 0; ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP); ch395f_set_des_ip(sock, ip_arr); ch395f_set_des_port(sock, PHASE2_REMOTE_PORT); for (i = 0; i < PHASE2_LOOP_COUNT; i++) { DBG_INFO("--- Iteration %d/%d ---", i + 1, PHASE2_LOOP_COUNT); iter_pass = 1; ch395f_set_sour_port(sock, PHASE2_LOCAL_PORT + i); if (ch395f_open_socket(sock) != CH395F_ERR_SUCCESS) { TEST_CHECK(0, "open socket"); iter_pass = 0; goto phase2_iter_done; } if (ch395f_tcp_connect(sock) != CH395F_ERR_SUCCESS) { TEST_CHECK(0, "tcp connect"); iter_pass = 0; ch395f_close_socket(sock); goto phase2_iter_done; } tick = HAL_GetTick(); while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) { int_st = ch395f_get_glob_int_status_all(); if (int_st & (1U << (sock + 4))) { sock_int = ch395f_get_sock_int_status(sock); if (sock_int & CH395F_SINT_STAT_CONNECT) break; } HAL_Delay(10); } if (HAL_GetTick() - tick >= PHASE2_TIMEOUT_MS) { TEST_CHECK(0, "connect timeout"); iter_pass = 0; ch395f_close_socket(sock); goto phase2_iter_done; } HAL_Delay(100); { uint8_t test_buf[64] = {0}; const char *msg = "HelloFromCH395F_TCP_Client! This is a 64-byte test message."; int p, mlen = (int)strlen(msg); memcpy(test_buf, msg, mlen); for (p = mlen; p < 64; p++) test_buf[p] = (uint8_t)('0' + (p - mlen) % 10); ch395f_write_send_buf(sock, test_buf, 64); } { int got_echo = 0; tick = HAL_GetTick(); while (HAL_GetTick() - tick < 10000) { uint16_t rlen = ch395f_get_recv_len(sock); if (rlen > 0) { uint16_t read_len = (rlen > sizeof(rx_buf)) ? (uint16_t)sizeof(rx_buf) : rlen; memset(rx_buf, 0, sizeof(rx_buf)); ch395f_read_recv_buf(sock, rx_buf, read_len); DBG_INFO("recv: %d bytes [%.30s]", read_len, (char *)rx_buf); ch395f_write_send_buf(sock, rx_buf, read_len); got_echo = 1; break; } int_st = ch395f_get_glob_int_status_all(); if (int_st & (1U << (sock + 4))) { sock_int = ch395f_get_sock_int_status(sock); if (sock_int & CH395F_SINT_STAT_DISCONNECT) { DBG_INFO("DISCONNECT"); break; } if (sock_int & CH395F_SINT_STAT_SOCK_TIMEOUT) { DBG_INFO("SOCK_TIMEOUT"); break; } } HAL_Delay(50); } if (!got_echo) { TEST_CHECK(0, "echo recv (iter %d)", i + 1); iter_pass = 0; } } phase2_iter_done: if (iter_pass) TEST_CHECK(1, "Iteration %d PASS", i + 1); ch395f_close_socket(sock); { uint8_t st[2]; uint32_t wait = HAL_GetTick(); while (HAL_GetTick() - wait < 5000) { ch395f_get_socket_status(sock, st); if (st[0] == CH395F_SOCKET_CLOSED) break; HAL_Delay(50); } DBG_INFO("closed: sock=0x%02X tcp=0x%02X", st[0], st[1]); } HAL_Delay(200); } TEST_REPORT("Phase 2"); } /* ============================ Phase 3 ============================ */ void ch395f_phase3_tests(void) { uint8_t sock = 0; uint8_t rx_buf[300] = {0}; uint32_t tick = 0; int i = 0; int echo_ok = 0; DBG_INFO("=== CH395F Phase 3 Tests (UDP) ==="); sock = 0; ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP); ch395f_set_sour_port(sock, PHASE3_LOCAL_PORT); { uint8_t zero_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF}; ch395f_set_des_ip(sock, zero_ip); } ch395f_set_des_port(sock, 0); TEST_CHECK(ch395f_open_socket(sock) == CH395F_ERR_SUCCESS, "UDP socket %d opened (port=%d)", sock, PHASE3_LOCAL_PORT); HAL_Delay(200); for (i = 0; i < PHASE3_ECHO_COUNT; i++) { tick = HAL_GetTick(); echo_ok = 0; while (HAL_GetTick() - tick < PHASE3_TIMEOUT_MS) { uint16_t rlen = ch395f_get_recv_len(sock); if (rlen > 8) { memset(rx_buf, 0, sizeof(rx_buf)); ch395f_read_recv_buf(sock, rx_buf, rlen); uint16_t payload_len = rlen - 8; uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8); uint8_t *src_ip = &rx_buf[4]; uint8_t *payload = &rx_buf[8]; DBG_INFO("UDP #%d: %dB from %d.%d.%d.%d:%d [%.32s]", i + 1, payload_len, src_ip[0], src_ip[1], src_ip[2], src_ip[3], src_port, payload_len > 0 ? (char *)payload : ""); ch395f_set_des_ip(sock, src_ip); ch395f_set_des_port(sock, src_port); ch395f_write_send_buf(sock, payload, payload_len); { uint32_t swait = HAL_GetTick(); while (HAL_GetTick() - swait < 2000) { uint16_t gs = ch395f_get_glob_int_status_all(); if (gs & (1U << (sock + 4))) { if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) break; } HAL_Delay(10); } } echo_ok = 1; break; } HAL_Delay(50); } TEST_CHECK(echo_ok, "UDP echo #%d/%d", i + 1, PHASE3_ECHO_COUNT); if (!echo_ok) break; } ch395f_close_socket(sock); TEST_CHECK(1, "UDP socket closed"); TEST_REPORT("Phase 3"); } /* ============================ Phase 4 ============================ */ /* * NET 层 TCP Echo(单连接模式) * 在 netTask 中运行,仅操作 Socket 0 * 覆盖:64B、1460B(MSS)、4096B(缓冲满) 回显 */ #ifdef ENABLE_PHASE4_TESTS static char s_phase4_buf[8192]; static uint32_t s_phase4_echo_count = 0; #endif void ch395f_phase4_tests(void) { #ifdef ENABLE_PHASE4_TESTS net_sock_t *sk = net_get_sock(0); if (sk == NULL || !sk->in_use) { return; } if (sk->state == NET_SOCK_STATE_ESTABLISHED) { int n = net_recv_sock(sk, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT); if (n > 0) { int sent = net_send_sock(sk, s_phase4_buf, n); if (sent == n) { s_phase4_echo_count++; if (s_phase4_echo_count <= 10 || (s_phase4_echo_count % 100 == 0)) { DBG_INFO("Phase4: echo #%lu (%d bytes)", s_phase4_echo_count, n); } } } } #endif } /* ============================ Phase 5 ============================ */ /* * NET 层大文件传输测试 * 验证超过 CH395F 内部缓冲区大小(4KB)的连续数据收发 * 覆盖:20KB、64KB 分块传输 */ #ifdef ENABLE_PHASE5_TESTS static int s_phase5_active = 0; static uint32_t s_phase5_total_sent = 0; static uint32_t s_phase5_total_recv = 0; static uint8_t s_phase5_tx_buf[PHASE5_FILE_SIZE]; static uint8_t s_phase5_rx_buf[PHASE5_BUF_SIZE]; static void phase5_fill_pattern(uint8_t *buf, uint32_t size, uint32_t offset) { for (uint32_t i = 0; i < size; i++) { buf[i] = (uint8_t)((offset + i) & 0xFF); } } static int phase5_verify_pattern(uint8_t *buf, uint32_t size, uint32_t offset) { for (uint32_t i = 0; i < size; i++) { if (buf[i] != (uint8_t)((offset + i) & 0xFF)) { DBG_ERROR("Phase5: verify fail at offset %lu (expected 0x%02X, got 0x%02X)", offset + i, (uint8_t)((offset + i) & 0xFF), buf[i]); return 0; } } return 1; } #endif void ch395f_phase5_init(void) { #ifdef ENABLE_PHASE5_TESTS s_phase5_active = 0; s_phase5_total_sent = 0; s_phase5_total_recv = 0; DBG_INFO("Phase5: file transfer test, size=%d bytes, chunk=%d bytes", PHASE5_FILE_SIZE, PHASE5_CHUNK_SIZE); #endif } void ch395f_phase5_tests(void) { #ifdef ENABLE_PHASE5_TESTS net_sock_t *sk = net_get_sock(0); if (sk == NULL || !sk->in_use) { return; } if (!s_phase5_active) { if (sk->state == NET_SOCK_STATE_ESTABLISHED) { s_phase5_active = 1; s_phase5_total_sent = 0; s_phase5_total_recv = 0; phase5_fill_pattern(s_phase5_tx_buf, PHASE5_FILE_SIZE, 0); DBG_INFO("Phase5: starting %d-byte file transfer (sock=0)", PHASE5_FILE_SIZE); } return; } /* 发送阶段:分块发送,每块 PHASE5_CHUNK_SIZE */ if (s_phase5_total_sent < PHASE5_FILE_SIZE) { uint32_t remaining = PHASE5_FILE_SIZE - s_phase5_total_sent; uint32_t chunk = (remaining > PHASE5_CHUNK_SIZE) ? PHASE5_CHUNK_SIZE : remaining; int sent = net_send_sock(sk, s_phase5_tx_buf + s_phase5_total_sent, (int)chunk); if (sent > 0) { s_phase5_total_sent += (uint32_t)sent; DBG_INFO("Phase5: sent %d/%d bytes", s_phase5_total_sent, PHASE5_FILE_SIZE); } return; } /* 接收阶段:分块接收并验证 */ if (s_phase5_total_recv < PHASE5_FILE_SIZE) { uint32_t remaining = PHASE5_FILE_SIZE - s_phase5_total_recv; int max_read = (int)(remaining < NET_TASK_BUF_SIZE ? remaining : NET_TASK_BUF_SIZE); int n = net_recv_sock(sk, s_phase5_rx_buf, max_read, NET_MSG_DONTWAIT); if (n > 0) { int ok = phase5_verify_pattern(s_phase5_rx_buf, (uint32_t)n, s_phase5_total_recv); TEST_CHECK(ok, "Phase5: recv verify at offset %lu (%d/%d bytes)", s_phase5_total_recv, s_phase5_total_recv + (uint32_t)n, PHASE5_FILE_SIZE); s_phase5_total_recv += (uint32_t)n; } return; } /* 完成 */ if (s_phase5_total_recv >= PHASE5_FILE_SIZE) { TEST_CHECK(1, "Phase5: %d-byte file transfer complete (sent=%lu, recv=%lu)", PHASE5_FILE_SIZE, s_phase5_total_sent, s_phase5_total_recv); s_phase5_active = 0; } #endif } /* ============================ Phase 6 ============================ */ #ifndef PHASE6_ANNOUNCE_PORT #define PHASE6_ANNOUNCE_PORT 60001 #endif #ifndef PHASE6_ANNOUNCE_SOCK #define PHASE6_ANNOUNCE_SOCK 6 #endif void ch395f_phase6_tests(void) { #ifdef ENABLE_PHASE6_TESTS uint8_t status = 0; uint8_t ip_info[20] = {0}; uint32_t tick = 0; DBG_INFO("=== CH395F Phase 6 Tests (DHCP) ==="); ch395f_set_dhcp(1); TEST_CHECK(1, "DHCP enabled, waiting for IP..."); HAL_Delay(500); tick = HAL_GetTick(); while (HAL_GetTick() - tick < 30000) { uint16_t gint = ch395f_get_glob_int_status_all(); if (gint & CH395F_GINT_STAT_DHCP) { DBG_INFO("DHCP interrupt"); } status = ch395f_get_dhcp_status(); if (status == 0x00) { TEST_CHECK(1, "DHCP: SUCCESS"); break; } HAL_Delay(500); } TEST_CHECK(HAL_GetTick() - tick < 30000, "DHCP completed within 30s (status=0x%02X)", status); if (HAL_GetTick() - tick >= 30000) { goto phase6_end; } ch395f_get_ip_inf(ip_info); DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); DBG_INFO(" MASK: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); TEST_CHECK(ip_info[0] != 0 || ip_info[1] != 0 || ip_info[2] != 0 || ip_info[3] != 0, "IP = %d.%d.%d.%d (non-zero)", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); { #define PHASE6_HELLO_PORT 60000 uint8_t anno_sock = PHASE6_ANNOUNCE_SOCK; uint8_t bcast_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF}; uint8_t rx_buf[64] = {0}; ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP); ch395f_set_send_buf(anno_sock, 24, 2); ch395f_set_recv_buf(anno_sock, 26, 2); ch395f_set_sour_port(anno_sock, PHASE6_HELLO_PORT); ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_port(anno_sock, 0); if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) { ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_port(anno_sock, PHASE6_ANNOUNCE_PORT); ch395f_write_send_buf(anno_sock, ip_info, 4); { uint32_t swait = HAL_GetTick(); while (HAL_GetTick() - swait < 2000) { uint16_t gs = ch395f_get_glob_int_status_all(); if (gs & (1U << (anno_sock + 4))) { if (ch395f_get_sock_int_status(anno_sock) & CH395F_SINT_STAT_SENBUF_FREE) break; } HAL_Delay(10); } } DBG_INFO("DHCP announcement: IP=%d.%d.%d.%d (port %d->%d)", ip_info[0], ip_info[1], ip_info[2], ip_info[3], PHASE6_HELLO_PORT, PHASE6_ANNOUNCE_PORT); TEST_CHECK(1, "DHCP announcement sent"); ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_port(anno_sock, 0); uint32_t wait = HAL_GetTick(); uint8_t hello_ok = 0; while (HAL_GetTick() - wait < 5000) { ch395f_get_glob_int_status_all(); uint16_t rlen = ch395f_get_recv_len(anno_sock); if (rlen > 8) { memset(rx_buf, 0, sizeof(rx_buf)); ch395f_read_recv_buf(anno_sock, rx_buf, rlen); uint16_t payload_len = rlen - 8; uint8_t *payload = &rx_buf[8]; if (payload_len == 5 && memcmp(payload, "HELLO", 5) == 0) { uint8_t *src_ip = &rx_buf[4]; uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8); ch395f_set_des_ip(anno_sock, src_ip); ch395f_set_des_port(anno_sock, src_port); ch395f_write_send_buf(anno_sock, payload, payload_len); HAL_Delay(100); DBG_INFO("HELLO response sent to %d.%d.%d.%d:%d", src_ip[0], src_ip[1], src_ip[2], src_ip[3], src_port); TEST_CHECK(1, "HELLO echo OK"); hello_ok = 1; break; } } HAL_Delay(50); } TEST_CHECK(hello_ok, "HELLO echo within 5s"); ch395f_close_socket(anno_sock); } else { TEST_CHECK(0, "DHCP announcement: open socket failed"); } } phase6_end: TEST_REPORT("Phase 6"); #endif } /* ============================ Phase 7 ============================ */ /* * Select/Poll I/O 多路复用测试(单连接模式) * 在 netTask 中运行,仅操作 Socket 0 */ #ifdef ENABLE_PHASE7_TESTS static char s_phase7_buf[8192]; static uint32_t s_phase7_count = 0; #endif void ch395f_phase7_tests(void) { #ifdef ENABLE_PHASE7_TESTS net_sock_t *sk = net_get_sock(0); if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED) { return; } net_fd_set readfds; net_timeval tv; NET_FD_ZERO(&readfds); NET_FD_SET(0, &readfds); tv.tv_sec = 0; tv.tv_usec = 50000; int n = net_select(1, &readfds, NULL, NULL, &tv); if (n <= 0) return; if (NET_FD_ISSET(0, &readfds)) { int len = net_recv_sock(sk, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT); if (len > 0) { net_send_sock(sk, s_phase7_buf, len); s_phase7_count++; TEST_CHECK(1, "Phase7: select echo #%lu (%d bytes)", s_phase7_count, len); } } #endif } /* ============================ Phase 8 ============================ */ /* * 边界条件测试(单连接模式,netTask 中运行) * 覆盖:零长度收发、DMA 边界(4095/4096/4097B)、 * recv 缓冲区小于数据、连续快速发送、快速重连 */ #ifdef ENABLE_PHASE8_TESTS static int s_phase8_stage = 0; /* 0=idle, 1=4095B, 2=4096B, 3=4097B, 4=zero, 5=fastsend */ static uint32_t s_phase8_sent = 0; static uint32_t s_phase8_recv = 0; static uint8_t s_phase8_tx[8192]; static uint8_t s_phase8_rx[8192]; static void phase8_fill(uint8_t *buf, uint32_t size, uint8_t seed) { for (uint32_t i = 0; i < size; i++) { buf[i] = (uint8_t)(seed + i); } } static int phase8_verify(uint8_t *buf, uint32_t size, uint8_t seed) { for (uint32_t i = 0; i < size; i++) { if (buf[i] != (uint8_t)(seed + i)) { return 0; } } return 1; } #endif void ch395f_phase8_init(void) { #ifdef ENABLE_PHASE8_TESTS s_phase8_stage = 0; DBG_INFO("Phase8: boundary tests initialized"); #endif } void ch395f_phase8_tests(void) { #ifdef ENABLE_PHASE8_TESTS net_sock_t *sk = net_get_sock(0); if (sk == NULL || !sk->in_use) { return; } if (sk->state == NET_SOCK_STATE_LISTENING) { /* 等待连接 */ return; } if (sk->state != NET_SOCK_STATE_ESTABLISHED) { s_phase8_stage = 0; return; } switch (s_phase8_stage) { case 0: { /* Stage 1: 发送 4095 字节(DMA 边界 -1:单次 SPI 事务可容纳) */ DBG_INFO("Phase8: stage 1/5 — send 4095 bytes (DMA boundary -1)"); phase8_fill(s_phase8_tx, 4095, 0x10); int sent = net_send_sock(sk, s_phase8_tx, 4095); TEST_CHECK(sent == 4095, "Phase8: send 4095 bytes (sent=%d)", sent); s_phase8_sent = (sent > 0) ? (uint32_t)sent : 0; s_phase8_recv = 0; s_phase8_stage = 1; break; } case 1: { /* 等待接收 4095 字节回显 */ int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, (int)(4095 - s_phase8_recv), NET_MSG_DONTWAIT); if (n > 0) { s_phase8_recv += (uint32_t)n; if (s_phase8_recv >= 4095) { int ok = phase8_verify(s_phase8_rx, 4095, 0x10); TEST_CHECK(ok, "Phase8: verify 4095 bytes echo"); s_phase8_stage = 2; } } break; } case 2: { /* Stage 2: 发送 4096 字节(DMA 边界:刚好填满 SPI 事务) */ DBG_INFO("Phase8: stage 2/5 — send 4096 bytes (DMA boundary exact)"); phase8_fill(s_phase8_tx, 4096, 0x20); int sent = net_send_sock(sk, s_phase8_tx, 4096); TEST_CHECK(sent == 4096, "Phase8: send 4096 bytes (sent=%d)", sent); s_phase8_sent = (sent > 0) ? (uint32_t)sent : 0; s_phase8_recv = 0; s_phase8_stage = 3; break; } case 3: { int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, (int)(4096 - s_phase8_recv), NET_MSG_DONTWAIT); if (n > 0) { s_phase8_recv += (uint32_t)n; if (s_phase8_recv >= 4096) { int ok = phase8_verify(s_phase8_rx, 4096, 0x20); TEST_CHECK(ok, "Phase8: verify 4096 bytes echo"); s_phase8_stage = 4; } } break; } case 4: { /* Stage 3: 发送 4097 字节(DMA 边界 +1:需两次 SPI 事务) */ DBG_INFO("Phase8: stage 3/5 — send 4097 bytes (DMA boundary +1, two SPI xacts)"); phase8_fill(s_phase8_tx, 4097, 0x30); int sent = net_send_sock(sk, s_phase8_tx, 4097); TEST_CHECK(sent == 4097, "Phase8: send 4097 bytes (sent=%d)", sent); s_phase8_sent = (sent > 0) ? (uint32_t)sent : 0; s_phase8_recv = 0; s_phase8_stage = 5; break; } case 5: { int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, (int)(4097 - s_phase8_recv), NET_MSG_DONTWAIT); if (n > 0) { s_phase8_recv += (uint32_t)n; if (s_phase8_recv >= 4097) { int ok = phase8_verify(s_phase8_rx, 4097, 0x30); TEST_CHECK(ok, "Phase8: verify 4097 bytes echo"); s_phase8_stage = 6; } } break; } case 6: { /* Stage 4: 零长度发送(验证错误处理) */ DBG_INFO("Phase8: stage 4/5 — zero-length send/recv"); int sent = net_send_sock(sk, s_phase8_tx, 0); TEST_CHECK(sent < 0, "Phase8: send 0 bytes returns %d (expected <0)", sent); s_phase8_stage = 7; break; } case 7: { /* Stage 5: 连续快速发送 20 个小包(验证 SENDBUF_FREE 等待机制) */ DBG_INFO("Phase8: stage 5/5 — rapid 20x 100-byte sends"); phase8_fill(s_phase8_tx, 100, 0x40); int all_ok = 1; for (int i = 0; i < 20; i++) { int sent = net_send_sock(sk, s_phase8_tx, 100); if (sent != 100) { TEST_CHECK(0, "Phase8: rapid send #%d failed (sent=%d)", i + 1, sent); all_ok = 0; break; } osDelay(1); } if (all_ok) { TEST_CHECK(1, "Phase8: 20x rapid sends OK"); s_phase8_sent = 2000; s_phase8_recv = 0; s_phase8_stage = 8; } else { s_phase8_stage = 0; } break; } case 8: { /* 接收所有快速发送的回显 */ int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, (int)(2000 - s_phase8_recv), NET_MSG_DONTWAIT); if (n > 0) { s_phase8_recv += (uint32_t)n; if (s_phase8_recv >= 2000) { TEST_CHECK(1, "Phase8: all 2000 bytes echo received"); TEST_REPORT("Phase 8"); s_phase8_stage = 0; } } break; } default: s_phase8_stage = 0; break; } #endif } /* ============================ Phase 9 ============================ */ /* * PHY 链路恢复测试(需物理插拔网线) * 在 main.c 中单独调用,或在 netTask 中通过宏启用 */ void ch395f_phase9_tests(void) { #ifdef ENABLE_PHASE9_TESTS DBG_INFO("=== CH395F Phase 9 Tests (PHY Recovery) ==="); /* TC-NET-901: 拔线后检测 PHY 断开 */ uint32_t wait = HAL_GetTick(); uint8_t phy_disconnected = 0; while (HAL_GetTick() - wait < 15000) { uint16_t gint = ch395f_get_glob_int_status_all(); if (gint & CH395F_GINT_STAT_PHY_CHANGE) { uint8_t phy = ch395f_get_phy_status(); DBG_INFO("PHY_CHANGE: 0x%02X", phy); if (phy == CH395F_PHY_DISCONN) { TEST_CHECK(1, "PHY disconnect detected"); phy_disconnected = 1; break; } } HAL_Delay(100); } if (!phy_disconnected) { TEST_CHECK(0, "PHY disconnect NOT detected within 15s (unplug cable?)"); goto phase9_end; } /* TC-NET-902: 插回网线后检测链路恢复 */ wait = HAL_GetTick(); uint8_t phy_reconnected = 0; while (HAL_GetTick() - wait < 30000) { uint16_t gint = ch395f_get_glob_int_status_all(); if (gint & CH395F_GINT_STAT_PHY_CHANGE) { uint8_t phy = ch395f_get_phy_status(); DBG_INFO("PHY_CHANGE: 0x%02X", phy); if (phy != CH395F_PHY_DISCONN) { TEST_CHECK(1, "PHY reconnected (0x%02X)", phy); phy_reconnected = 1; break; } } HAL_Delay(100); } if (!phy_reconnected) { TEST_CHECK(0, "PHY reconnect NOT detected within 30s (re-plug cable?)"); } phase9_end: TEST_REPORT("Phase 9"); #endif } /* ============================ Phase 10 ============================ */ /* * KeepAlive 超时断开测试 * 在 netTask 中运行,监视 Socket 0 的 DISCONNECT/TIMEOUT 事件 * PC 连接后静置(不发数据),验证 KeepAlive 在空闲超时后触发断开 */ #ifdef ENABLE_PHASE10_TESTS static int s_phase10_connected = 0; static uint32_t s_phase10_start_tick = 0; #endif void ch395f_phase10_tests(void) { #ifdef ENABLE_PHASE10_TESTS net_sock_t *sk = net_get_sock(0); if (sk == NULL || !sk->in_use) { return; } if (!s_phase10_connected) { if (sk->state == NET_SOCK_STATE_ESTABLISHED) { s_phase10_connected = 1; s_phase10_start_tick = HAL_GetTick(); DBG_INFO("Phase10: connection established, waiting for KeepAlive timeout (%dms)...", PHASE10_KEEPALIVE_IDLE_MS); } return; } /* 已建立连接,等待断开(KeepAlive 触发) */ if (sk->state == NET_SOCK_STATE_LISTENING) { uint32_t elapsed = HAL_GetTick() - s_phase10_start_tick; TEST_CHECK(elapsed >= PHASE10_KEEPALIVE_IDLE_MS, "Phase10: KeepAlive triggered after %lums (idle=%dms)", elapsed, PHASE10_KEEPALIVE_IDLE_MS); s_phase10_connected = 0; TEST_REPORT("Phase 10"); } #endif }