// 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 #include #include #include #include #include #include #include #include #include #include #include #include /** * Simple buffer for passing data between TCP sockets and DPI modules */ const int BUFSIZE_BYTE = 25600; 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; }