/** * @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 #include /* ============================================================ * 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); }