网络主要内容测试完成

This commit is contained in:
2026-08-26 13:54:52 +08:00
parent 0a707034cc
commit 88160a5134
18 changed files with 2174 additions and 928 deletions

View File

@@ -925,7 +925,7 @@ uint8_t ch395f_get_dhcp_status(void) {
/*
* 函数功能设置Socket协议类型
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 入口参数sock - Socket索引 uint8_t 0 - 7
* proto - 协议类型 uint8_t CH395F_PROTO_TYPE_xxx
* 限定条件Socket必须处于关闭状态
*/
@@ -948,7 +948,7 @@ void ch395f_set_proto_type(uint8_t sock, uint8_t proto) {
/*
* 函数功能设置Socket目标IP地址
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 入口参数sock - Socket索引 uint8_t 0 - 7
* p_ip - 4字节目标IP指针 uint8_t*
* 限定条件p_ip不可为NULL
*/
@@ -980,7 +980,7 @@ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip) {
/*
* 函数功能设置Socket目标端口小端格式
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 入口参数sock - Socket索引 uint8_t 0 - 7
* port - 目标端口号 uint16_t
*/
void ch395f_set_des_port(uint8_t sock, uint16_t port) {
@@ -1005,7 +1005,7 @@ void ch395f_set_des_port(uint8_t sock, uint16_t port) {
/*
* 函数功能设置Socket源端口小端格式
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 入口参数sock - Socket索引 uint8_t 0 - 7
* port - 源端口号 uint16_t
*/
void ch395f_set_sour_port(uint8_t sock, uint16_t port) {
@@ -1105,7 +1105,7 @@ uint8_t ch395f_tcp_disconnect(uint8_t sock) {
/*
* 函数功能向Socket发送缓冲区写入数据DMA批量传输
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 入口参数sock - Socket索引 uint8_t 0 - 7
* p_data - 数据指针 uint8_t*
* len - 数据长度 uint16_t
* 限定条件1. p_data不可为NULL
@@ -1149,7 +1149,7 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
/*
* 函数功能从Socket接收缓冲区读取数据DMA批量传输
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 入口参数sock - Socket索引 uint8_t 0 - 7
* p_data - 输出缓冲区指针 uint8_t*
* len - 待读取数据长度 uint16_t
* 限定条件1. p_data不可为NULL
@@ -1193,8 +1193,8 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
/*
* 函数功能获取Socket接收缓冲区中数据长度
* 入口参数sock - Socket索引 uint8_t 0 - 3
* 返回值:接收数据长度 uint16_t
* 入口参数sock - Socket索引 uint8_t 0 - 7
* 返回值:接收数据长度 uint16_t,实际上 ch395F 接收缓冲区最大也就 8192
* 限定条件Socket必须已打开
*/
uint16_t ch395f_get_recv_len(uint8_t sock) {

View File

@@ -110,12 +110,31 @@ extern "C" {
/* 3 字节输入 + 数据输入socket + 长度 + 数据) */
#define CH395F_CMD_WRITE_SEND_BUF_SN 0x39U
/*
* TCP MSSCH395F_CMD_SET_TCP_MSS 参数)
* 由 net_init 经 ch395f_set_tcp_mss() 下发;此处作为单一真值源,
* DMA 缓冲大小亦由此推导见下。当前选取依据8 Socket 共享 24KB 缓冲时,
* 各 Socket 接收缓冲 = 2×MSS故 MSS 取 1024 为最大值(详见 net_socket.c 分配注释)。
*/
#define CH395F_TCP_MSS 1024U
/* 内部缓冲 RAM 每块字节数(手册 §5.4548 块 × 512B = 24KB */
#define CH395F_RAM_BLOCK_SIZE 512U
/* 单 Socket 缓冲块数由 MSS 推导:接收 = 2×MSS满足手册 §9.2.8 接收 ≥ 2×MSS发送 = 1×MSS向上取整 */
#define CH395F_RECV_BLOCKS ((2U * CH395F_TCP_MSS + CH395F_RAM_BLOCK_SIZE - 1U) / CH395F_RAM_BLOCK_SIZE)
#define CH395F_SEND_BLOCKS ((CH395F_TCP_MSS + CH395F_RAM_BLOCK_SIZE - 1U) / CH395F_RAM_BLOCK_SIZE)
/* 单 Socket 硬件收发缓冲字节数(= 块数 × 块大小)。写入发送 FIFO 的单次长度严禁超过此值,
* 否则会写穿有限的发送缓冲(见 Trap 20。 */
#define CH395F_SEND_BUF_SIZE_BYTES (CH395F_SEND_BLOCKS * CH395F_RAM_BLOCK_SIZE)
#define CH395F_RECV_BUF_SIZE_BYTES (CH395F_RECV_BLOCKS * CH395F_RAM_BLOCK_SIZE)
/*
* SPI DMA 事务限制
* CH395F_SPI_DMA_MAX_PAYLOAD: 单次 DMA 事务最大有效载荷(总缓冲 - 4 字节命令头)
* 注意: CH395F 内部 socket 接收缓冲默认 4096BDMA 缓冲需至少 4100 以一次性读空
* 接收方向需一次性读空整个 Socket 接收缓冲(= 2×MSS避免 Trap 13 部分读导致数据不可见),
* 故总缓冲 = 2×MSS + 44 为命令头开销),有效载荷 = 2×MSS。
*/
#define CH395F_SPI_DMA_BUF_SIZE 4100U
#define CH395F_SPI_DMA_BUF_SIZE (CH395F_TCP_MSS * 2U + 4U)
#define CH395F_SPI_DMA_MAX_PAYLOAD (CH395F_SPI_DMA_BUF_SIZE - 4U)
/* 其他命令 */

View File

@@ -41,13 +41,16 @@ static int check_socket_error(int sockfd);
* timeout - 超时时间结构体指针 net_timeval *NULL=无限等待)
* 出口参数readfds/writefds/exceptfds - 仅保留就绪的 Socket 位 net_fd_set *
* 返回值:就绪的 Socket 数量(>= 0-1 表示参数无效 int
* 限定条件net_init() + MX_FREERTOS_Init() 已执行;必须在 netTask 上下文调用
* 函数说明1. 轮询所有 Socket,检查读/写/异常事件是否就绪
* 2. 阻塞等待直到有事件就绪或超时
* 3. 通过 osDelay(1) 让出 CPU平衡实时性与 CPU 利用率
* 限定条件net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用
* 函数说明1. 轮询 Socket 控制块RAM中的读/写/异常状态,判断事件是否就绪
* 2. 不在此直接访问 CH395F SPI——所有 ch395f 事务统一由 netTask 的 net_poll()
* 每 10ms 串行执行并刷新控制块状态send_ready / readable本函数仅做
* RAM 读取,避免与应用任务发起的 SPI 访问竞争(见 Trap 16
* 3. 阻塞等待直到有事件就绪或超时
* 4. 通过 osDelay(1) 让出 CPU平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms
*/
int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
net_fd_set *exceptfds, net_timeval *timeout) {
net_fd_set *exceptfds, net_timeval *timeout) {
uint32_t start_tick;
uint32_t timeout_ms = 0;
net_fd_set result_read = {0};
@@ -70,9 +73,7 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
start_tick = HAL_GetTick();
while (1) {
/* 轮询所有 Socket */
net_poll();
/* 仅读取 netTask 的 net_poll() 已刷新的控制块状态(无 CH395F SPI 访问) */
ready_count = 0;
/* 检查哪个 Socket 有事件 */
@@ -146,10 +147,11 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
* timeout - 超时时间(毫秒) int >= 0-1=无限等待,但本实现按 >=0 处理)
* 出口参数fds[i].revents - 填充实际发生的事件掩码 short
* 返回值:就绪的 fd 数量(>= 0-1 表示参数无效 int
* 限定条件net_init() + MX_FREERTOS_Init() 已执行;必须在 netTask 上下文调用
* 函数说明1. 轮询所有 Socket,检查读/写/错误/挂起事件是否就绪
* 2. 阻塞等待直到有事件就绪或超时
* 3. 通过 osDelay(1) 让出 CPU平衡实时性与 CPU 利用率
* 限定条件net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用
* 函数说明1. 轮询 Socket 控制块RAM中的读/写/错误/挂起状态,判断事件是否就绪
* 2. 不在此直接访问 CH395F SPI——状态由 netTask 的 net_poll() 每 10ms 刷新
* 3. 阻塞等待直到有事件就绪或超时
* 4. 通过 osDelay(1) 让出 CPU平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms
*/
int net_poll_events(net_pollfd *fds, int nfds, int timeout) {
uint32_t start_tick;
@@ -163,9 +165,7 @@ int net_poll_events(net_pollfd *fds, int nfds, int timeout) {
start_tick = HAL_GetTick();
while (1) {
/* 轮询 */
net_poll();
/* 仅读取 netTask 的 net_poll() 已刷新的控制块状态(无 CH395F SPI 访问) */
ready = 0;
for (i = 0; i < nfds; i++) {
@@ -248,7 +248,6 @@ int net_poll_events(net_pollfd *fds, int nfds, int timeout) {
*/
static int check_socket_readable(int sockfd) {
net_sock_t *p_sock;
uint16_t recv_len;
p_sock = net_get_sock(sockfd);
if (NULL == p_sock) {
@@ -260,12 +259,10 @@ static int check_socket_readable(int sockfd) {
return 1;
}
/* 已建立连接的 Socket 有数据可读 */
if (NET_SOCK_STATE_ESTABLISHED == p_sock->state) {
recv_len = ch395f_get_recv_len(sockfd);
if (0 < recv_len) {
return 1;
}
/* 已建立连接或 UDP 等有数据可读readable 由 net_poll 在 RECV_OK 时置位、
* net_recv_locked 读空后清零。避免应用任务直接访问 CH395F SPI见 Trap 16 */
if (p_sock->readable) {
return 1;
}
return 0;

View File

@@ -65,7 +65,9 @@ static void parse_ip_string(const char *str, uint8_t *ip_arr);
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len);
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags);
static int net_close_locked(net_sock_t *p_sock);
static int net_relisten_locked(net_sock_t *p_sock);
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen);
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen);
/*
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
@@ -104,13 +106,18 @@ int net_init(const char *ip, const char *mask, const char *gateway) {
ch395f_reset();
DBG_INFO("net_init: set_fun_para...");
/* 启用单连接模式 + 手动关闭 Socketbit3=1, bit1=0
/* 多 Socket 模式 + TCP Server 单连接 + 手动关闭 Socketbit0=1, bit1=0, bit3=1
* bit0=1: 多 Socket 模式,启用 Socket 0~7单 Socket 模式下 1~7 被禁用)
* bit1=0: TCP Server 单连接,监听 Socket 0 仅接受 1 个客户端(连接停留在 Socket 0 本身)
* bit3=1: DISCONNECT/TIMEOUT 时不自动清空缓冲区,由软件排空数据后手动关闭 */
ch395f_set_fun_para(0x08);
ch395f_set_fun_para(0x09);
DBG_INFO("net_init: set_tcp_mss...");
/* 设置 TCP MSS */
ch395f_set_tcp_mss(1460);
/* 设置 TCP MSS
* 手册(§5.45)CH395F 内部缓冲共 48 块 × 512B = 24KB。下方为 8 个 Socket 各分配 6 块
* (接收 4 块=2KB + 发送 2 块=1KB按手册 §9.2.8(接收 ≥ 2×MSS 建议)取 MSS=1024用满 48 块。
* MSS 真值源为 ch395f.h 的 CH395F_TCP_MSSDMA 缓冲大小亦由其推导。 */
ch395f_set_tcp_mss(CH395F_TCP_MSS);
/* 设置 TCP 重传参数(必须在 INIT_CH395 之前调用*/
ch395f_set_retrans_count(8); /* 重传 8 次*/
@@ -141,13 +148,31 @@ int net_init(const char *ip, const char *mask, const char *gateway) {
ch395f_set_dhcp(1);
}
/* 初始化 CH395F 协议栈(先不配置 buffer测试默认值是否工作 */
/* 初始化 CH395F 协议栈 */
ret = ch395f_init();
if (ret != CH395F_STATUS_OK) {
s_net_errno = NET_ERR;
return -1;
}
/* 多 Socket 模式:为 Socket 0~7 各分配独立收发缓冲(必须在 OPEN_SOCKET 之前)
* 手册(§5.45)CH395F 内部缓冲共 48 块 × 512B = 24KB芯片上电默认仅给 Socket 0~3
* 分配(每 Socket 接收 8 块/发送 4 块Socket 4~7 为 NULL故用到 8 个 Socket 必须重分配。
* 单 Socket 块数由 MSS 推导ch395f.h接收 CH395F_RECV_BLOCKS(=2×MSS/512=4 块=2KB)、
* 发送 CH395F_SEND_BLOCKS(=1×MSS/512=2 块=1KB),每 Socket 共 6 块MSS=1024 用满 48 块:
* blocks_per_socket = CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS
* recv_start = i * blocks_per_socket send_start = recv_start + CH395F_RECV_BLOCKS
* Socket 7 占块 42~47恰好用尽 0~47互不重叠。
* 注:此布局以手册 §5.4548×512B为准若实际芯片为其它缓冲规格需重新核算。
* Trap 01先配数据 Socket 1~7再配监听 Socket 0。 */
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
for (int i = NET_MAX_SOCKETS - 1; i >= 0; i--) {
uint8_t recv_start = (uint8_t)(i * blocks_per_socket);
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
ch395f_set_send_buf((uint8_t)i, send_start, (uint8_t)CH395F_SEND_BLOCKS);
ch395f_set_recv_buf((uint8_t)i, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
/* 使能 PING 响应 */
ch395f_ping_enable(1);
@@ -207,9 +232,10 @@ int net_poll(void) {
/* 取本 Socket 控制块(直接用数组下标,无需 net_get_sock 的越界判断) */
net_sock_t *p_sock = &s_net_socks[i];
/* 全局中断状态 bit4~bit11 分别对应 socket0~socket7 的中断标志,
* 对应位为 0 说明该 Socket 无中断,跳过以节省时间 */
if (!(int_status & (1U << (i + 4)))) {
/* 全局中断状态GET_GLOB_INT_STATUS_ALL 16 位Socket n 的中断位即 bit(n+4)。
* Socket 0~3 在低位字节 bit4~bit7Socket 4~7 在高位字节的 bit0~3
* (即 16 位值的 bit8~bit11。对应位为 0 说明该 Socket 无中断,跳过以节省时间。 */
if (!(int_status & (1U << (i + 4U)))) {
continue;
}
@@ -307,7 +333,7 @@ void net_process_messages(void) {
break;
}
case NET_MSG_CONNECT: {
msg.result = net_connect(msg.sockfd,
msg.result = net_connect_locked(msg.sockfd,
(const struct net_sockaddr *)&msg.addr,
msg.addrlen);
break;
@@ -364,6 +390,7 @@ int net_socket(int domain, int type, int protocol) {
p_sock->state = NET_SOCK_STATE_CREATED;
p_sock->in_use = 1;
p_sock->send_ready = 1;
p_sock->readable = 0;
return sockfd;
}
@@ -419,10 +446,14 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
if (p_sock->type == NET_SOCK_DGRAM) {
uint8_t status;
/* Socket 4-7 默认无收发缓冲,需显式分配 */
/* net_init 已为全部 8 个 Socket 分配缓冲;此处仅对 Socket 4~7 兜底重设,
* 使用与 net_init 相同的布局(由 MSS 推导,见 net_init */
if (sockfd >= 4) {
ch395f_set_send_buf((uint8_t)sockfd, 28, 2);
ch395f_set_recv_buf((uint8_t)sockfd, 30, 2);
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
uint8_t recv_start = (uint8_t)(sockfd * blocks_per_socket);
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
ch395f_set_send_buf((uint8_t)sockfd, send_start, (uint8_t)CH395F_SEND_BLOCKS);
ch395f_set_recv_buf((uint8_t)sockfd, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_UDP);
@@ -505,6 +536,8 @@ int net_listen_locked(int sockfd) {
/* 监听 Socket 进入 LISTENING 态 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* 单连接模式:连接断开后由 net 层自动回到 LISTEN模拟 BSD 监听常驻) */
p_sock->auto_relisten = 1;
/* 单连接CONNECT 中断直接作用于本 Socket 自身 */
/*
@@ -584,6 +617,11 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
/* 单连接:本 Socket 自身在 CONNECT 后已转为 ESTABLISHED
* 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接 */
if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* 连接已断开CLOSED且启用自动重监听先把 Socket 重新置回 LISTEN
* 下一次 accept 即可等待新连接BSD 风格监听常驻) */
if (p_listen_sock->state == NET_SOCK_STATE_CLOSED && p_listen_sock->auto_relisten) {
net_relisten_locked(p_listen_sock);
}
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
@@ -652,22 +690,72 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
}
/*
* 函数功能TCP Client 发起与服务器的连接
* 函数功能TCP Client 发起与服务器的连接(线程安全版本,通过消息队列委托给 netTask
* 入口参数sockfd - Socket 描述符 (0~7),必须为已创建的 TCP Socket int
* addr - 服务器地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值0 - 连接请求发送成功(异步,需等待 ESTABLISHED int
* -1 - 连接失败(参数无效、状态错误等) int
* 限定条件Socket 已通过 net_socket() 创建且 type == NET_SOCK_STREAM尚未连接
* 函数说明:设置目标 IP 和端口后打开 CH395F Socket进入 CONNECTING 状态。连接建立需等待
* net_poll() 检测到 CONNECT 事件并将状态更新为 ESTABLISHED
* 函数说明:将连接请求放入消息队列,阻塞等待 netTask 处理完毕。实际的 SPI 操作在 netTask
* 上下文中执行,避免与 net_poll() 的 SPI 访问产生竞争(否则会破坏 CH395F 寄存器)。
* 连接建立需等待 net_poll() 检测到 CONNECT 事件并将状态更新为 ESTABLISHED
*/
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
net_msg_t msg;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (NULL == netMsgQueueHandle) {
s_net_errno = NET_ERR;
return -1;
}
if (NULL == addr || addrlen < (int)sizeof(struct net_sockaddr_in)) {
s_net_errno = NET_ERR_INVAL;
return -1;
}
msg.type = NET_MSG_CONNECT;
msg.sockfd = sockfd;
msg.buf = NULL;
msg.len = 0;
msg.flags = 0;
memcpy(&msg.addr, addr, sizeof(struct net_sockaddr_in));
msg.addrlen = addrlen;
msg.caller = xTaskGetCurrentTaskHandle();
msg.result = -1;
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
{
uint32_t s_notified;
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
msg.result = (int)s_notified;
}
if (msg.result < 0) {
s_net_errno = NET_ERR;
}
return msg.result;
}
/*
* 函数功能TCP Client 发起连接netTask 内部锁定版本,由消息队列委托执行)
* 限定条件net_init() 已调用;必须在 netTask 上下文中调用
*/
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen) {
net_sock_t *p_sock;
const struct net_sockaddr_in *p_addr_in;
uint8_t status;
/* 参数检<EFBFBD>?*/
/* 参数检*/
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
@@ -690,24 +778,56 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
p_sock->remote_ip = p_addr_in->sin_addr.s_addr;
p_sock->remote_port = net_ntohs(p_addr_in->sin_port);
/* 转换 IP 为数组格式(低字节在前) */
p_sock->remote_ip_arr[0] = (uint8_t)(p_sock->remote_ip);
p_sock->remote_ip_arr[1] = (uint8_t)(p_sock->remote_ip >> 8);
p_sock->remote_ip_arr[2] = (uint8_t)(p_sock->remote_ip >> 16);
p_sock->remote_ip_arr[3] = (uint8_t)(p_sock->remote_ip >> 24);
/* 转换 IP 为数组格式(大端/网络字节序,与 ch395f set_des_ip 期望一致,
* 同 Phase2 phase2_tc201 的 test_parse_ip 结果 [192,168,1,2] */
p_sock->remote_ip_arr[0] = (uint8_t)(p_sock->remote_ip >> 24);
p_sock->remote_ip_arr[1] = (uint8_t)(p_sock->remote_ip >> 16);
p_sock->remote_ip_arr[2] = (uint8_t)(p_sock->remote_ip >> 8);
p_sock->remote_ip_arr[3] = (uint8_t)(p_sock->remote_ip);
/* 配置 CH395F Socket */
/* 配置 CH395F Socket:先 OPEN 再写源/目的端口(避免 open 复位寄存器) */
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
ch395f_set_sour_port(sockfd, p_sock->local_port);
ch395f_set_des_ip(sockfd, p_sock->remote_ip_arr);
ch395f_set_des_port(sockfd, p_sock->remote_port);
status = ch395f_open_socket(sockfd);
/* CH395F 关闭 Socket 后需延时才能重新打开Trap 09故失败重试最多 3 次 */
for (int open_try = 0; open_try < 3; open_try++) {
status = ch395f_open_socket(sockfd);
if (status == CH395F_ERR_SUCCESS) {
break; /* 打开成功,退出重试 */
}
ch395f_close_socket(sockfd); /* 打开失败先关闭,再延时后重试 */
HAL_Delay(500);
}
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
/* 清掉本 Socket 可能残留的中断状态,丢弃上一条连接遗留的 DISCONNECT */
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
(void)ch395f_get_glob_int_status_all();
ch395f_set_sour_port(sockfd, p_sock->local_port);
ch395f_set_des_ip(sockfd, p_sock->remote_ip_arr);
ch395f_set_des_port(sockfd, p_sock->remote_port);
/* 诊断:回读目的 IP/端口与 Socket 状态GET_REMOT_IPP 为小端,故需按小端解析) */
{
uint8_t rip[6];
uint8_t st = 0;
uint32_t dip;
uint16_t dp;
ch395f_get_remot_ipp((uint8_t)sockfd, rip);
ch395f_get_socket_status((uint8_t)sockfd, &st);
dip = ((uint32_t)rip[3] << 24) | ((uint32_t)rip[2] << 16) |
((uint32_t)rip[1] << 8) | rip[0];
dp = ((uint16_t)rip[5] << 8) | rip[4];
DBG_INFO("[CONN] sock%d openOK dest=%d.%d.%d.%d:%d status=0x%02X",
sockfd,
(dip >> 24) & 0xFF, (dip >> 16) & 0xFF,
(dip >> 8) & 0xFF, dip & 0xFF, dp, st);
}
/* 发起 TCP 连接 */
ch395f_tcp_connect(sockfd);
p_sock->state = NET_SOCK_STATE_CONNECTING;
@@ -719,6 +839,11 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
* SPI DMA 单次事务最大有效载荷(取 CH395F 驱动公开常量)
*/
#define NET_DMA_MAX_PAYLOAD ((int)CH395F_SPI_DMA_MAX_PAYLOAD)
/* 单次写入 CH395F 发送 FIFO 的最大字节数:受硬件发送缓冲(CH395F_SEND_BUF_SIZE_BYTES=1KB)
* 与 SPI DMA 载荷(NET_DMA_MAX_PAYLOAD=2KB) 双重限制,以硬件发送缓冲为硬上限(见 Trap 20
* 否则单次写 2KB/4KB 会写穿 1KB 发送 FIFO。 */
#define NET_SEND_CHUNK_MAX ((int)CH395F_SEND_BUF_SIZE_BYTES < NET_DMA_MAX_PAYLOAD ? \
(int)CH395F_SEND_BUF_SIZE_BYTES : NET_DMA_MAX_PAYLOAD)
/*
* 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用)
@@ -731,7 +856,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
* 出口参数:无
* 返回值:实际发送字节数(>= 0-1 表示参数无效或 Socket 未连接 int
* 限定条件p_sock 非空Socket 处于 ESTABLISHED 状态netTask 上下文
* 函数说明1. 按 NET_DMA_MAX_PAYLOAD 分块写入 CH395F 发送缓冲区
* 函数说明1. 按 NET_SEND_CHUNK_MAX硬件发送缓冲上限见 Trap 20分块写入 CH395F 发送 FIFO
* 2. 发送前等待 send_ready 置位,超时返回已发送字节数
* 3. 每写完一块等待 SEND_OK 中断send_ready 清零后重新置位)
*/
@@ -754,8 +879,8 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
while (sent < len) {
int chunk = len - sent;
if (chunk > NET_DMA_MAX_PAYLOAD) {
chunk = NET_DMA_MAX_PAYLOAD;
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
if (!p_sock->send_ready) {
@@ -786,9 +911,6 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
return sent;
}
/*
* 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
*/
/*
* 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数p_sock - Socket 控制块指针 net_sock_t *
@@ -803,7 +925,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
* 3. CLOSED 状态尝试排空接收缓冲区残余数据Trap 13FIN 可能先于数据到达)
*/
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
uint16_t recv_len;
int recv_len;
uint32_t tick_start;
int sockfd = (int)(p_sock - s_net_socks);
@@ -819,15 +941,24 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
recv_len = ch395f_get_recv_len(sockfd);
if (recv_len > 0) {
int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len;
int read_len = (recv_len > len) ? len : recv_len;
DBG_INFO("recv sock%d len=%d", sockfd, recv_len);
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len;
p_sock->readable = 0; /* 已消费数据(注:部分读受 Trap 13 限制,下一次 RECV_OK 会重新置位) */
HAL_Delay(1); /* 等待 CH395F 刷新 recv_len 寄存器 */
return read_len;
}
if (flags & NET_MSG_DONTWAIT) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
if (p_sock->auto_relisten) {
net_relisten_locked(p_sock);
}
return 0;
}
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
@@ -840,13 +971,18 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
/* 断开前尝试排空 CH395F 接收缓冲区Trap 13FIN 可能比数据先到) */
uint16_t rem = ch395f_get_recv_len(sockfd);
int rem = ch395f_get_recv_len(sockfd);
if (rem > 0) {
int rd = (rem > (uint16_t)len) ? len : (int)rem;
int rd = (rem > len) ? len : rem;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)rd);
p_sock->recv_bytes += (uint32_t)rd;
p_sock->readable = 0;
return rd;
}
/* EOF单连接模式自动回到 LISTEN应用下次 net_accept 即接到新连接 */
if (p_sock->auto_relisten) {
net_relisten_locked(p_sock);
}
return 0;
}
}
@@ -864,10 +1000,82 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
* 函数说明1. 调用 ch395f_close_socket 关闭 CH395F 硬件 Socket
* 2. 清空整个控制块memset 0释放该 Socket 资源
*/
static int net_close_locked(net_sock_t *p_sock) {
/*
* 函数功能:单连接模式断连后自动重新进入 LISTEN不清空控制块保留 fd
* 入口参数p_sock - 已断开CLOSED的 TCP 监听型 Socket 控制块 net_sock_t *
* 返回值0 - 成功,-1 - 失败
* 限定条件netTask 上下文p_sock->in_use == 1保留 local_port/auto_relisten
* 函数说明1. 复用原本地端口与协议,重新 OPEN + TCP_LISTENTrap 09 重试)
* 2. 仅将 state 置回 LISTENING不 memsetfd 与 auto_relisten 保持不变)
* 3. 实现 BSD 风格"监听 Socket 常驻",应用层无需手动重监听
*/
static int net_relisten_locked(net_sock_t *p_sock) {
uint8_t status;
int sockfd = (int)(p_sock - s_net_socks);
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_TCP);
ch395f_set_sour_port((uint8_t)sockfd, p_sock->local_port);
for (int open_try = 0; open_try < 3; open_try++) {
status = ch395f_open_socket((uint8_t)sockfd);
if (status == CH395F_ERR_SUCCESS) {
break;
}
ch395f_close_socket((uint8_t)sockfd); /* 打开失败先关闭,再延时后重试 */
HAL_Delay(500);
}
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
status = ch395f_tcp_listen((uint8_t)sockfd);
if (status != CH395F_ERR_SUCCESS) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
p_sock->state = NET_SOCK_STATE_LISTENING;
p_sock->remote_port = 0;
p_sock->remote_ip = 0;
memset(p_sock->remote_ip_arr, 0, sizeof(p_sock->remote_ip_arr));
p_sock->send_ready = 0;
p_sock->recv_bytes = 0;
/* auto_relisten 保持为 1下次断连仍自动重监听 */
for (int j = 0; j < NET_MAX_SOCKETS; j++) {
(void)ch395f_get_sock_int_status((uint8_t)j);
}
(void)ch395f_get_glob_int_status_all();
DBG_INFO("sock%d auto-relisten ok", sockfd);
return 0;
}
static int net_close_locked(net_sock_t *p_sock) {
int sockfd = (int)(p_sock - s_net_socks);
uint32_t tick = HAL_GetTick();
ch395f_close_socket(sockfd);
/* 排空上一条连接残留的 DISCONNECT 中断(单连接模式仅有 Socket 0
* 复用同一 Socket 时,旧连接的 DISCONNECT 在 FIN/ACK 完成后才上报,
* 若不清除会被下一连接误判为"刚建立就断开")。等待芯片回到 CLOSED
* 并清掉 Socket/全局中断状态,避免事件串扰。
*/
do {
uint8_t st = 0;
ch395f_get_socket_status((uint8_t)sockfd, &st);
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
(void)ch395f_get_glob_int_status_all();
if (st == CH395F_SOCKET_CLOSED) {
break;
}
if (HAL_GetTick() - tick > 500) {
break;
}
osDelay(5);
} while (1);
memset(p_sock, 0, sizeof(net_sock_t));
return 0;
@@ -876,47 +1084,61 @@ static int net_close_locked(net_sock_t *p_sock) {
/*
* 函数功能:发送数据(线程安全版本,通过消息队列委托给 netTask
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* flags - 标志位,通常为 0 int
* buf - 待发送数据缓冲区指针(调用返回前须保持有效,本函数不拷贝数据) const void *
* len - 数据长度(字节),> 0 int
* flags - 标志位,当前实现忽略(保留 BSD send 接口兼容) int
* 返回值:实际发送字节数 int >= 0 || -1
* -1 - 发送失败(参数无效、队列满等) int
* 限定条件:Socket 处于 ESTABLISHED 状态net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将发送请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_send_sock()
* -1 - 发送失败(参数非法 / 消息队列入队失败 / netTask 侧发送错误) int
* 限定条件:1. 必须在【非 netTask】上下文中调用应用/测试任务);若在 netTask 内调用会自己等自己
* netTask 阻塞在 xTaskNotifyWait 而无人处理消息)→ 死锁。netTask 内请直接用 net_send_locked()
* 2. Socket 须处于 ESTABLISHED 状态net_init() + MX_FREERTOS_Init() 已执行;
* netMsgQueueHandle 已由 net_init 创建。
* 函数说明:所有 CH395F SPI 访问只允许在 netTask 内串行执行(见 Trap 16。本函数作为应用侧 API
* 自身不直接触碰 SPI而是把发送请求打包成 net_msg_t 投递到 netMsgQueueHandle由 netTask
* 的 net_process_messages() 取出并在 netTask 上下文调用 net_send_locked() 真正写 CH395F。
* 调用方通过 FreeRTOS TaskNotify 阻塞等待处理结果,从而对外呈现"同步发送"语义:
* 函数返回即代表本次发送已被 netTask 处理完毕(成功或失败)。
*/
int net_send(int sockfd, const void *buf, int len, int flags) {
(void)flags;
net_msg_t msg;
(void)flags; /* flags 当前未使用,显式 void 以消除未用参数告警 */
net_msg_t msg; /* 一次性消息结构体;入队后由 netTask 取出处理(注意只传指针不拷贝数据) */
/* ---- 1. 参数合法性检查 ---- */
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
s_net_errno = NET_ERR_BADF; /* 描述符越界 */
return -1;
}
/* ---- 2. 运行环境检查:消息队列须已创建(网络层已初始化) ---- */
if (NULL == netMsgQueueHandle) {
s_net_errno = NET_ERR;
s_net_errno = NET_ERR; /* 网络层未初始化 */
return -1;
}
msg.type = NET_MSG_SEND;
msg.sockfd = sockfd;
msg.buf = (void *)buf;
msg.len = len;
msg.flags = 0;
msg.caller = xTaskGetCurrentTaskHandle();
msg.result = -1;
/* ---- 3. 组装消息:把本次 send 的全部上下文填入 msg ---- */
msg.type = NET_MSG_SEND; /* 消息类型netTask 据此分派到 net_send_locked */
msg.sockfd = sockfd; /* 目标 Socket */
msg.buf = (void *)buf; /* 数据缓冲队列仅拷贝指针buf 须有效至函数返回) */
msg.len = len; /* 待发送字节数 */
msg.flags = 0; /* 实际标志位(忽略入参 flags */
msg.caller = xTaskGetCurrentTaskHandle(); /* 记录调用任务句柄,供 netTask 回执时精准通知 */
msg.result = -1; /* 预置失败netTask 处理成功后会改写为实际发送字节数 */
/* ---- 4. 入队:将消息投递给 netTaskosWaitForever 表示队列满时阻塞调用方 ---- */
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY;
s_net_errno = NET_ERR_BUSY; /* 入队失败(队列异常等极端情况) */
return -1;
}
/* ---- 5. 阻塞等待 netTask 处理完毕netTask 调 net_send_locked 后通过
* xTaskNotify() 唤醒本任务,并把结果(实际发送字节数或 -1作为通知值传回 ---- */
{
uint32_t s_notified;
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY);
msg.result = (int)s_notified;
xTaskNotifyWait(0, 0, &s_notified, portMAX_DELAY); /* 一直阻塞直到被 netTask 通知 */
msg.result = (int)s_notified; /* 取回 netTask 写入的结果 */
}
/* ---- 6. 结果回传:负数视为错误并设置线程级错误码;否则返回实际发送字节数 ---- */
if (msg.result < 0) {
s_net_errno = NET_ERR;
}
@@ -1057,13 +1279,13 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
if (NULL != dest_addr && addrlen >= (int)sizeof(struct net_sockaddr_in)) {
p_addr_in = (const struct net_sockaddr_in *)dest_addr;
/* 转换 IP 为数组格<EFBFBD>?*/
/* 转换 IP 为大端数组(同 TCP 客户端set_des_ip 期望大端) */
uint8_t ip_arr[4];
uint32_t ip = p_addr_in->sin_addr.s_addr;
ip_arr[0] = (uint8_t)(ip);
ip_arr[1] = (uint8_t)(ip >> 8);
ip_arr[2] = (uint8_t)(ip >> 16);
ip_arr[3] = (uint8_t)(ip >> 24);
ip_arr[0] = (uint8_t)(ip >> 24);
ip_arr[1] = (uint8_t)(ip >> 16);
ip_arr[2] = (uint8_t)(ip >> 8);
ip_arr[3] = (uint8_t)(ip);
ch395f_set_des_ip(sockfd, ip_arr);
ch395f_set_des_port(sockfd, net_ntohs(p_addr_in->sin_port));
@@ -1074,8 +1296,8 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
/* 发送数<E98081>?*/
while (sent < len) {
int chunk = len - sent;
if (chunk > NET_SEND_BUF_SIZE) {
chunk = NET_SEND_BUF_SIZE;
if (chunk > NET_SEND_CHUNK_MAX) {
chunk = NET_SEND_CHUNK_MAX;
}
/* 检查发送缓冲区 */
@@ -1122,7 +1344,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen) {
net_sock_t *p_sock;
uint16_t recv_len;
int recv_len;
uint32_t tick_start;
/* 参数检<E695B0>?*/
@@ -1172,7 +1394,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
/* 读取数据 */
if (recv_len > 0) {
int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len;
int read_len = (recv_len > len) ? len : recv_len;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len;
return read_len;
@@ -1505,6 +1727,7 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
static void handle_recv_event(net_sock_t *p_sock) {
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len
* 避免和 net_recv_locked 里面的 ch395f_get_recv_len 冲突Trap 13 */
p_sock->readable = 1; /* 置可读标志,供 net_select 在无 SPI 访问下判读 */
fire_event(p_sock, NET_EVENT_DATA_RECEIVED);
}

View File

@@ -149,7 +149,7 @@ typedef struct {
uint16_t local_port; /* 本地端口(主机序) */
uint16_t remote_port; /* 远端端口(主机序) */
uint32_t remote_ip; /* 远端 IP网络字节序 */
uint8_t remote_ip_arr[4]; /* 远端 IP 数组(低字节在前 */
uint8_t remote_ip_arr[4]; /* 远端 IP 数组(大端/网络字节序 */
/* 事件回调(可选) */
net_event_cb_t event_cb;
@@ -158,6 +158,14 @@ typedef struct {
/* 发送就绪标志 */
uint8_t send_ready;
/* 接收可读标志net_poll 检测到 RECV_OK 时置 1net_recv_locked 读空后清零。
* net_select 据此判断可读性,从而无需在应用/测试任务上下文直接访问 CH395F SPI
* (所有 ch395f 事务统一由 netTask 串行执行,避免与 net_poll 竞争,见 Trap 16。 */
uint8_t readable;
/* 单连接模式自动重监听:连接断开后由 net 层自动回到 LISTENBSD 风格监听常驻) */
uint8_t auto_relisten;
/* 统计信息 */
uint32_t recv_bytes; /* 累计接收字节数 */
uint32_t send_bytes; /* 累计发送字节数 */