448 lines
13 KiB
C
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);
|
|
}
|