2026-03-14 21:14:26 +08:00

448 lines
13 KiB
C

/**
* @file qdx_port.c
* @brief FreeRTOS + WCHNET implementation of the QDX HAL (qdx_port.h)
*
* Bridges the WCHNET proprietary TCP/IP stack with the blocking/thread-based
* interface expected by qdx_tcp_logic. Uses binary semaphores and ring buffers
* to convert WCHNET async callbacks into blocking recv semantics.
*/
#include "qdx_port.h"
#include "wchnet.h"
#include "eth_driver.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <string.h>
#include <stdio.h>
/* ============================================================
* Ring-Buffer for receive data
* ============================================================ */
#define RX_RING_SIZE 2920 /* == WCHNET_TCP_MSS * 2 */
typedef struct {
uint8_t buf[RX_RING_SIZE];
uint16_t head; /* write index */
uint16_t tail; /* read index */
uint16_t count; /* bytes stored */
} RxRingBuf_t;
static void ring_init(RxRingBuf_t *r)
{
r->head = 0;
r->tail = 0;
r->count = 0;
}
static uint16_t ring_available(const RxRingBuf_t *r)
{
return r->count;
}
static uint16_t ring_write(RxRingBuf_t *r, const uint8_t *data, uint16_t len)
{
uint16_t free = RX_RING_SIZE - r->count;
if (len > free) len = free;
for (uint16_t i = 0; i < len; i++) {
r->buf[r->head] = data[i];
r->head = (r->head + 1) % RX_RING_SIZE;
}
r->count += len;
return len;
}
static uint16_t ring_read(RxRingBuf_t *r, uint8_t *data, uint16_t max_len)
{
uint16_t to_read = (r->count < max_len) ? r->count : max_len;
for (uint16_t i = 0; i < to_read; i++) {
data[i] = r->buf[r->tail];
r->tail = (r->tail + 1) % RX_RING_SIZE;
}
r->count -= to_read;
return to_read;
}
/* ============================================================
* Socket Context — maps WCHNET socket ID to qdx_socket_t
* ============================================================ */
#define MAX_SOCKETS 2
typedef struct {
uint8_t in_use;
uint8_t wchnet_sock_id;
volatile uint8_t connected;
RxRingBuf_t rx_ring;
SemaphoreHandle_t rx_sem; /* binary sem: data available */
SemaphoreHandle_t connect_sem; /* binary sem: connect complete */
} SocketCtx_t;
static SocketCtx_t g_sock_ctx[MAX_SOCKETS];
/* WCHNET per-socket receive buffers (required by WCHNET_ModifyRecvBuf) */
extern u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN];
/* WCHNET mutex for thread-safe access to WCHNET APIs */
static SemaphoreHandle_t g_wchnet_mutex;
/* ============================================================
* Internal helpers
* ============================================================ */
static SocketCtx_t *alloc_sock_ctx(void)
{
for (int i = 0; i < MAX_SOCKETS; i++) {
if (!g_sock_ctx[i].in_use) {
memset(&g_sock_ctx[i], 0, sizeof(SocketCtx_t));
g_sock_ctx[i].in_use = 1;
ring_init(&g_sock_ctx[i].rx_ring);
g_sock_ctx[i].rx_sem = xSemaphoreCreateBinary();
g_sock_ctx[i].connect_sem = xSemaphoreCreateBinary();
return &g_sock_ctx[i];
}
}
return NULL;
}
static void free_sock_ctx(SocketCtx_t *ctx)
{
if (!ctx) return;
if (ctx->rx_sem) { vSemaphoreDelete(ctx->rx_sem); ctx->rx_sem = NULL; }
if (ctx->connect_sem) { vSemaphoreDelete(ctx->connect_sem); ctx->connect_sem = NULL; }
ctx->in_use = 0;
ctx->connected = 0;
}
static SocketCtx_t *find_ctx_by_wchnet_id(uint8_t sockid)
{
for (int i = 0; i < MAX_SOCKETS; i++) {
if (g_sock_ctx[i].in_use && g_sock_ctx[i].wchnet_sock_id == sockid)
return &g_sock_ctx[i];
}
return NULL;
}
/** Parse dotted-decimal IP string to 4-byte array */
static int parse_ip(const char *ip_str, uint8_t ip[4])
{
unsigned int a, b, c, d;
int n = 0, idx = 0;
const char *p = ip_str;
a = b = c = d = 0;
uint32_t vals[4] = {0};
while (*p && idx < 4) {
if (*p >= '0' && *p <= '9') {
vals[idx] = vals[idx] * 10 + (*p - '0');
} else if (*p == '.') {
idx++;
} else {
return -1;
}
p++;
}
if (idx != 3) return -1;
for (int i = 0; i < 4; i++) {
if (vals[i] > 255) return -1;
ip[i] = (uint8_t)vals[i];
}
return 0;
}
/* ============================================================
* Public: called from WCHNET_HandleSockInt (task context)
* ============================================================ */
void qdx_port_sock_recv_notify(uint8_t sockid)
{
SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
if (!ctx) return;
/* Read data from WCHNET into ring buffer */
uint8_t tmp[512];
uint32_t len;
uint32_t total = 0;
while (1) {
len = sizeof(tmp);
uint8_t err = WCHNET_SocketRecv(sockid, tmp, &len);
if (len == 0) break;
ring_write(&ctx->rx_ring, tmp, (uint16_t)len);
total += len;
if (err != WCHNET_ERR_SUCCESS) break;
}
DBG_PORT("recv_notify sock%d: %u bytes, ring=%u\r\n",
sockid, (unsigned)total, (unsigned)ring_available(&ctx->rx_ring));
/* Wake blocking recv thread */
xSemaphoreGive(ctx->rx_sem);
}
void qdx_port_sock_connect_notify(uint8_t sockid)
{
SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
if (!ctx) return;
ctx->connected = 1;
DBG_PORT("connect_notify sock%d\r\n", sockid);
xSemaphoreGive(ctx->connect_sem);
}
void qdx_port_sock_disconnect_notify(uint8_t sockid)
{
SocketCtx_t *ctx = find_ctx_by_wchnet_id(sockid);
if (!ctx) return;
ctx->connected = 0;
DBG_PORT("disconnect_notify sock%d\r\n", sockid);
/* Wake recv thread so it can detect disconnect */
xSemaphoreGive(ctx->rx_sem);
}
/* ============================================================
* Init — call once before scheduler starts
* ============================================================ */
void qdx_port_init(void)
{
memset(g_sock_ctx, 0, sizeof(g_sock_ctx));
g_wchnet_mutex = xSemaphoreCreateMutex();
DBG_PORT("init done, mutex=%p\r\n", g_wchnet_mutex);
}
/* ============================================================
* Time & Delay
* ============================================================ */
uint32_t qdx_port_get_tick_ms(void)
{
return xTaskGetTickCount() * portTICK_PERIOD_MS;
}
void qdx_port_delay_ms(uint32_t ms)
{
vTaskDelay(pdMS_TO_TICKS(ms));
}
/* ============================================================
* Mutex
* ============================================================ */
qdx_mutex_t qdx_port_mutex_create(void)
{
return (qdx_mutex_t)xSemaphoreCreateMutex();
}
void qdx_port_mutex_lock(qdx_mutex_t mutex)
{
if (mutex)
xSemaphoreTake((SemaphoreHandle_t)mutex, portMAX_DELAY);
}
void qdx_port_mutex_unlock(qdx_mutex_t mutex)
{
if (mutex)
xSemaphoreGive((SemaphoreHandle_t)mutex);
}
void qdx_port_mutex_delete(qdx_mutex_t mutex)
{
if (mutex)
vSemaphoreDelete((SemaphoreHandle_t)mutex);
}
/* ============================================================
* Thread
* ============================================================ */
int8_t qdx_port_thread_create(const char *name, qdx_thread_entry_t entry,
void *arg, uint32_t stack_size, uint8_t priority)
{
/* stack_size from caller is in bytes; xTaskCreate expects words */
uint32_t stack_words = stack_size / sizeof(StackType_t);
if (stack_words < configMINIMAL_STACK_SIZE)
stack_words = configMINIMAL_STACK_SIZE;
BaseType_t ret = xTaskCreate((TaskFunction_t)entry, name,
(uint16_t)stack_words, arg,
(UBaseType_t)priority, NULL);
DBG_PORT("thread_create \"%s\" stack=%d pri=%d -> %s\r\n",
name, (int)stack_words, (int)priority, (ret == pdPASS) ? "OK" : "FAIL");
return (ret == pdPASS) ? 0 : -1;
}
/* ============================================================
* TCP Socket — connect / send / recv / close
* ============================================================ */
qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port)
{
uint8_t dest_ip[4];
if (parse_ip(ip, dest_ip) != 0) {
DBG_PORT("bad IP \"%s\"\r\n", ip);
return NULL;
}
DBG_PORT("connecting to %s:%d\r\n", ip, port);
SocketCtx_t *ctx = alloc_sock_ctx();
if (!ctx) {
DBG_PORT("no free SocketCtx\r\n");
return NULL;
}
SOCK_INF sock_inf;
memset(&sock_inf, 0, sizeof(sock_inf));
sock_inf.ProtoType = PROTO_TYPE_TCP;
sock_inf.SourPort = 0; /* let WCHNET assign ephemeral port */
sock_inf.DesPort = port;
memcpy(sock_inf.IPAddr, dest_ip, 4);
DBG_PORT("SOCK_INF: proto=%d dst=%d.%d.%d.%d:%d\r\n",
sock_inf.ProtoType,
sock_inf.IPAddr[0], sock_inf.IPAddr[1],
sock_inf.IPAddr[2], sock_inf.IPAddr[3],
sock_inf.DesPort);
uint8_t wchnet_id = 0;
xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
uint8_t err = WCHNET_SocketCreat(&wchnet_id, &sock_inf);
xSemaphoreGive(g_wchnet_mutex);
if (err != WCHNET_ERR_SUCCESS) {
DBG_PORT("SocketCreat fail %02X\r\n", err);
free_sock_ctx(ctx);
return NULL;
}
DBG_PORT("SocketCreat OK, wchnet_id=%d\r\n", wchnet_id);
ctx->wchnet_sock_id = wchnet_id;
/* Assign WCHNET receive buffer */
WCHNET_ModifyRecvBuf(wchnet_id,
(uint32_t)SocketRecvBuf[wchnet_id], RECE_BUF_LEN);
xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
err = WCHNET_SocketConnect(wchnet_id);
xSemaphoreGive(g_wchnet_mutex);
if (err != WCHNET_ERR_SUCCESS) {
DBG_PORT("SocketConnect fail %02X\r\n", err);
WCHNET_SocketClose(wchnet_id, 0);
free_sock_ctx(ctx);
return NULL;
}
DBG_PORT("SocketConnect OK (err=0x%02X), waiting connect_sem (5s)...\r\n", err);
/* Block until SINT_STAT_CONNECT or 5s timeout */
uint32_t t0 = xTaskGetTickCount();
BaseType_t sem_ret = xSemaphoreTake(ctx->connect_sem, pdMS_TO_TICKS(5000));
uint32_t elapsed = (xTaskGetTickCount() - t0) * portTICK_PERIOD_MS;
if (sem_ret != pdTRUE) {
DBG_PORT("connect_sem TIMEOUT after %d ms -> %d.%d.%d.%d:%d\r\n",
(int)elapsed, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port);
DBG_PORT(" ctx->connected=%d wchnet_id=%d\r\n", ctx->connected, wchnet_id);
WCHNET_SocketClose(wchnet_id, 0);
free_sock_ctx(ctx);
return NULL;
}
DBG_PORT("connect_sem got after %d ms, connected=%d\r\n", (int)elapsed, ctx->connected);
if (!ctx->connected) {
WCHNET_SocketClose(wchnet_id, 0);
free_sock_ctx(ctx);
return NULL;
}
#if KEEPALIVE_ENABLE
WCHNET_SocketSetKeepLive(wchnet_id, ENABLE);
#endif
DBG_PORT("connected sock %d -> %d.%d.%d.%d:%d\r\n",
wchnet_id, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], port);
return (qdx_socket_t)ctx;
}
int32_t qdx_port_tcp_send(qdx_socket_t sock, const uint8_t *data, uint32_t len)
{
SocketCtx_t *ctx = (SocketCtx_t *)sock;
if (!ctx || !ctx->connected)
return -1;
uint32_t send_len = len;
xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
uint8_t err = WCHNET_SocketSend(ctx->wchnet_sock_id, (uint8_t *)data, &send_len);
xSemaphoreGive(g_wchnet_mutex);
if (err != WCHNET_ERR_SUCCESS)
return -1;
return (int32_t)send_len;
}
int32_t qdx_port_tcp_recv(qdx_socket_t sock, uint8_t *buf, uint32_t max_len)
{
SocketCtx_t *ctx = (SocketCtx_t *)sock;
if (!ctx)
return -1;
/* Check ring buffer first */
uint16_t got = ring_read(&ctx->rx_ring, buf, (uint16_t)max_len);
if (got > 0)
return (int32_t)got;
/* No data — check if disconnected */
if (!ctx->connected)
return -1;
/* Block on semaphore up to 100ms */
xSemaphoreTake(ctx->rx_sem, pdMS_TO_TICKS(100));
/* Try again after wake */
got = ring_read(&ctx->rx_ring, buf, (uint16_t)max_len);
if (got > 0)
return (int32_t)got;
/* Still no data — disconnected? */
if (!ctx->connected)
return -1;
return 0; /* timeout, no data */
}
void qdx_port_tcp_close(qdx_socket_t sock)
{
SocketCtx_t *ctx = (SocketCtx_t *)sock;
if (!ctx) return;
if (ctx->in_use) {
xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
WCHNET_SocketClose(ctx->wchnet_sock_id, 0);
xSemaphoreGive(g_wchnet_mutex);
}
ring_init(&ctx->rx_ring);
free_sock_ctx(ctx);
}
/* ============================================================
* WCHNET global lock — exposed for task_wchnet_entry in main.c
* All WCHNET API calls must be serialized through this mutex.
* ============================================================ */
void qdx_port_net_lock(void)
{
if (g_wchnet_mutex)
xSemaphoreTake(g_wchnet_mutex, portMAX_DELAY);
}
void qdx_port_net_unlock(void)
{
if (g_wchnet_mutex)
xSemaphoreGive(g_wchnet_mutex);
}