| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #include "tcp_server.h" | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <netinet/in.h> | |
| #include <netinet/tcp.h> | |
| #include <pthread.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/socket.h> | |
| #include <sys/time.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| /** | |
| * Simple buffer for passing data between TCP sockets and DPI modules | |
| */ | |
| const int BUFSIZE_BYTE = 256; | |
| struct tcp_buf { | |
| unsigned int rptr; | |
| unsigned int wptr; | |
| char buf[BUFSIZE_BYTE]; | |
| }; | |
| /** | |
| * TCP Server thread context structure | |
| */ | |
| struct tcp_server_ctx { | |
| // Writeable by the host thread | |
| char *display_name; | |
| uint16_t listen_port; | |
| volatile bool socket_run; | |
| // Writeable by the server thread | |
| tcp_buf *buf_in; | |
| tcp_buf *buf_out; | |
| int sfd; // socket fd | |
| int cfd; // client fd | |
| pthread_t sock_thread; | |
| }; | |
| static bool tcp_buffer_is_full(struct tcp_buf *buf) { | |
| if (buf->wptr >= buf->rptr) { | |
| return (buf->wptr - buf->rptr) == (BUFSIZE_BYTE - 1); | |
| } else { | |
| return (buf->rptr - buf->wptr) == 1; | |
| } | |
| } | |
| static bool tcp_buffer_is_empty(struct tcp_buf *buf) { | |
| return (buf->wptr == buf->rptr); | |
| } | |
| static void tcp_buffer_put_byte(struct tcp_buf *buf, char dat) { | |
| bool done = false; | |
| while (!done) { | |
| if (!tcp_buffer_is_full(buf)) { | |
| buf->buf[buf->wptr++] = dat; | |
| buf->wptr %= BUFSIZE_BYTE; | |
| done = true; | |
| } | |
| } | |
| } | |
| static bool tcp_buffer_get_byte(struct tcp_buf *buf, char *dat) { | |
| if (tcp_buffer_is_empty(buf)) { | |
| return false; | |
| } | |
| *dat = buf->buf[buf->rptr++]; | |
| buf->rptr %= BUFSIZE_BYTE; | |
| return true; | |
| } | |
| static struct tcp_buf *tcp_buffer_new(void) { | |
| struct tcp_buf *buf_new; | |
| buf_new = (struct tcp_buf *)malloc(sizeof(struct tcp_buf)); | |
| buf_new->rptr = 0; | |
| buf_new->wptr = 0; | |
| return buf_new; | |
| } | |
| static void tcp_buffer_free(struct tcp_buf **buf) { | |
| free(*buf); | |
| *buf = NULL; | |
| } | |
| /** | |
| * Start a TCP server | |
| * | |
| * This function creates attempts to create a new TCP socket instance. The | |
| * socket is a non-blocking stream socket, with buffering disabled. | |
| * | |
| * @param ctx context object | |
| * @return 0 on success, -1 in case of an error | |
| */ | |
| static int start(struct tcp_server_ctx *ctx) { | |
| int rv; | |
| assert(ctx->sfd == 0 && "Server already started."); | |
| // create socket | |
| int sfd = socket(AF_INET, SOCK_STREAM, 0); | |
| if (sfd == -1) { | |
| fprintf(stderr, "%s: Unable to create socket: %s (%d)\n", ctx->display_name, | |
| strerror(errno), errno); | |
| return -1; | |
| } | |
| rv = fcntl(sfd, F_SETFL, O_NONBLOCK); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Unable to make socket non-blocking: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| return -1; | |
| } | |
| // reuse existing socket (if existing) | |
| int reuse_socket = 1; | |
| rv = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Unable to set socket options: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| return -1; | |
| } | |
| // stop tcp socket from buffering (buffering prevents timely responses to | |
| // OpenOCD which severly limits debugging performance) | |
| int tcp_nodelay = 1; | |
| rv = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int)); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Unable to set socket nodelay: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| return -1; | |
| } | |
| // bind server | |
| struct sockaddr_in addr; | |
| memset(&addr, 0, sizeof(addr)); | |
| addr.sin_family = AF_INET; | |
| addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
| addr.sin_port = htons(ctx->listen_port); | |
| rv = bind(sfd, (struct sockaddr *)&addr, sizeof(addr)); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Failed to bind socket: %s (%d)\n", ctx->display_name, | |
| strerror(errno), errno); | |
| return -1; | |
| } | |
| // listen for incoming connections | |
| rv = listen(sfd, 1); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Failed to listen on socket: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| return -1; | |
| } | |
| ctx->sfd = sfd; | |
| assert(ctx->sfd > 0); | |
| return 0; | |
| } | |
| /** | |
| * Accept an incoming connection from a client (nonblocking) | |
| * | |
| * The resulting client fd is made non-blocking. | |
| * | |
| * @param ctx context object | |
| * @return 0 on success, any other value indicates an error | |
| */ | |
| static int client_tryaccept(struct tcp_server_ctx *ctx) { | |
| int rv; | |
| assert(ctx->sfd > 0); | |
| assert(ctx->cfd == 0); | |
| int cfd = accept(ctx->sfd, NULL, NULL); | |
| if (cfd == -1 && errno == EAGAIN) { | |
| return -EAGAIN; | |
| } | |
| if (cfd == -1) { | |
| fprintf(stderr, "%s: Unable to accept incoming connection: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| return -1; | |
| } | |
| rv = fcntl(cfd, F_SETFL, O_NONBLOCK); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Unable to make client socket non-blocking: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| return -1; | |
| } | |
| ctx->cfd = cfd; | |
| assert(ctx->cfd > 0); | |
| printf("%s: Accepted client connection\n", ctx->display_name); | |
| return 0; | |
| } | |
| /** | |
| * Stop the TCP server | |
| * | |
| * @param ctx context object | |
| */ | |
| static void stop(struct tcp_server_ctx *ctx) { | |
| assert(ctx); | |
| if (!ctx->sfd) { | |
| return; | |
| } | |
| close(ctx->sfd); | |
| ctx->sfd = 0; | |
| } | |
| /** | |
| * Receive a byte from a connected client | |
| * | |
| * @param ctx context object | |
| * @param cmd byte received | |
| * @return true if a byte was read | |
| */ | |
| static bool get_byte(struct tcp_server_ctx *ctx, char *cmd) { | |
| assert(ctx); | |
| ssize_t num_read = read(ctx->cfd, cmd, 1); | |
| if (num_read == 0) { | |
| return false; | |
| } | |
| if (num_read == -1) { | |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
| return false; | |
| } else if (errno == EBADF) { | |
| // Possibly client went away? Accept a new connection. | |
| fprintf(stderr, "%s: Client disappeared.\n", ctx->display_name); | |
| tcp_server_client_close(ctx); | |
| return false; | |
| } else { | |
| fprintf(stderr, "%s: Error while reading from client: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| assert(0 && "Error reading from client"); | |
| } | |
| } | |
| assert(num_read == 1); | |
| return true; | |
| } | |
| /** | |
| * Send a byte to a connected client | |
| * | |
| * @param ctx context object | |
| * @param cmd byte to send | |
| */ | |
| static void put_byte(struct tcp_server_ctx *ctx, char cmd) { | |
| while (1) { | |
| ssize_t num_written = send(ctx->cfd, &cmd, sizeof(cmd), MSG_NOSIGNAL); | |
| if (num_written == -1) { | |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { | |
| continue; | |
| } else if (errno == EPIPE) { | |
| printf("%s: Remote disconnected.\n", ctx->display_name); | |
| tcp_server_client_close(ctx); | |
| break; | |
| } else { | |
| fprintf(stderr, "%s: Error while writing to client: %s (%d)\n", | |
| ctx->display_name, strerror(errno), errno); | |
| assert(0 && "Error writing to client."); | |
| } | |
| } | |
| if (num_written >= 1) { | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * Cleanup server context | |
| * | |
| * @param ctx context object | |
| */ | |
| static void ctx_free(struct tcp_server_ctx *ctx) { | |
| // Free the buffers | |
| tcp_buffer_free(&ctx->buf_in); | |
| tcp_buffer_free(&ctx->buf_out); | |
| // Free the display name | |
| free(ctx->display_name); | |
| // Free the ctx | |
| free(ctx); | |
| ctx = NULL; | |
| } | |
| /** | |
| * Thread function to create a new server instance | |
| * | |
| * @param ctx_void context object | |
| * @return Always returns NULL | |
| */ | |
| static void *server_create(void *ctx_void) { | |
| // Cast to a server struct | |
| struct tcp_server_ctx *ctx = (struct tcp_server_ctx *)ctx_void; | |
| struct timeval timeout; | |
| // Start the server | |
| int rv = start(ctx); | |
| if (rv != 0) { | |
| fprintf(stderr, "%s: Unable to create TCP server on port %d\n", | |
| ctx->display_name, ctx->listen_port); | |
| goto err_cleanup_return; | |
| } | |
| // Initialise timeout | |
| timeout.tv_sec = 0; | |
| // Initialise fd_set | |
| // Start waiting for connection / data | |
| char xfer_data; | |
| while (ctx->socket_run) { | |
| // Initialise structure of fds | |
| fd_set read_fds; | |
| FD_ZERO(&read_fds); | |
| if (ctx->sfd) { | |
| FD_SET(ctx->sfd, &read_fds); | |
| } | |
| if (ctx->cfd) { | |
| FD_SET(ctx->cfd, &read_fds); | |
| } | |
| // max fd num | |
| int mfd = (ctx->cfd > ctx->sfd) ? ctx->cfd : ctx->sfd; | |
| // Set timeout - 50us gives good performance | |
| timeout.tv_usec = 50; | |
| // Wait for socket activity or timeout | |
| rv = select(mfd + 1, &read_fds, NULL, NULL, &timeout); | |
| if (rv < 0) { | |
| printf("%s: Socket read failed, port: %d\n", ctx->display_name, | |
| ctx->listen_port); | |
| tcp_server_client_close(ctx); | |
| } | |
| // New connection | |
| if (FD_ISSET(ctx->sfd, &read_fds)) { | |
| client_tryaccept(ctx); | |
| } | |
| // New client data | |
| if (FD_ISSET(ctx->cfd, &read_fds)) { | |
| while (get_byte(ctx, &xfer_data)) { | |
| tcp_buffer_put_byte(ctx->buf_in, xfer_data); | |
| } | |
| } | |
| if (ctx->cfd != 0) { | |
| while (tcp_buffer_get_byte(ctx->buf_out, &xfer_data)) { | |
| put_byte(ctx, xfer_data); | |
| } | |
| } | |
| } | |
| err_cleanup_return: | |
| // Simulation done - clean up | |
| tcp_server_client_close(ctx); | |
| stop(ctx); | |
| return NULL; | |
| } | |
| // Abstract interface functions | |
| tcp_server_ctx *tcp_server_create(const char *display_name, int listen_port) { | |
| struct tcp_server_ctx *ctx = | |
| (struct tcp_server_ctx *)calloc(1, sizeof(struct tcp_server_ctx)); | |
| assert(ctx); | |
| // Create the buffers | |
| struct tcp_buf *buf_in = tcp_buffer_new(); | |
| struct tcp_buf *buf_out = tcp_buffer_new(); | |
| assert(buf_in); | |
| assert(buf_out); | |
| // Populate the struct with buffer pointers | |
| ctx->buf_in = buf_in; | |
| ctx->buf_out = buf_out; | |
| // Set up socket details | |
| ctx->socket_run = true; | |
| ctx->listen_port = listen_port; | |
| ctx->display_name = strdup(display_name); | |
| assert(ctx->display_name); | |
| if (pthread_create(&ctx->sock_thread, NULL, server_create, (void *)ctx) != | |
| 0) { | |
| fprintf(stderr, "%s: Unable to create TCP socket thread\n", | |
| ctx->display_name); | |
| ctx_free(ctx); | |
| free(ctx); | |
| return NULL; | |
| } | |
| return ctx; | |
| } | |
| bool tcp_server_read(struct tcp_server_ctx *ctx, char *dat) { | |
| return tcp_buffer_get_byte(ctx->buf_in, dat); | |
| } | |
| void tcp_server_write(struct tcp_server_ctx *ctx, char dat) { | |
| tcp_buffer_put_byte(ctx->buf_out, dat); | |
| } | |
| void tcp_server_close(struct tcp_server_ctx *ctx) { | |
| // Shut down the socket thread | |
| ctx->socket_run = false; | |
| pthread_join(ctx->sock_thread, NULL); | |
| ctx_free(ctx); | |
| } | |
| void tcp_server_client_close(struct tcp_server_ctx *ctx) { | |
| assert(ctx); | |
| if (!ctx->cfd) { | |
| return; | |
| } | |
| close(ctx->cfd); | |
| ctx->cfd = 0; | |
| } |