From 15966f94b5afa1803377d4c67eb65a84af96b8da Mon Sep 17 00:00:00 2001 From: Jian Jun Chen Date: Tue, 27 Nov 2018 08:27:26 +0800 Subject: [PATCH] dm: uart: add uart over tcp support This patch adds the support of inet socket as the backend of uart emulation. Data written by guest uart is transferred to a socket and data from socket is forwarded to guest uart. This enables something called "Uart Over TCP" which is useful in some case such as WinDbg connection over uart. The command line syntax is as follows: -l comX,tcp:port_number Tracked-On: #2962 Signed-off-by: Jian Jun Chen Acked-by: Yin Fengwei --- devicemodel/hw/uart_core.c | 655 +++++++++++++++++++++++++++---------- 1 file changed, 474 insertions(+), 181 deletions(-) diff --git a/devicemodel/hw/uart_core.c b/devicemodel/hw/uart_core.c index 341b14d5c..6a56dbe81 100644 --- a/devicemodel/hw/uart_core.c +++ b/devicemodel/hw/uart_core.c @@ -36,6 +36,9 @@ #include #include #include +#include +#include +#include #include #include "types.h" @@ -43,6 +46,7 @@ #include "uart_core.h" #include "ns16550.h" #include "dm.h" +#include "dm_string.h" #define COM1_BASE 0x3F8 #define COM1_IRQ 4 @@ -63,7 +67,12 @@ #define REG_SCR com_scr #endif -#define FIFOSZ 256 +#define DEFAULT_FIFOSZ (256) +#define SOCK_FIFOSZ (32 * 1024) + +static int uart_debug; +#define DPRINTF(params) do { if (uart_debug) printf params; } while (0) +#define WPRINTF(params) (printf params) static struct termios tio_stdio_orig; @@ -78,19 +87,40 @@ static struct { #define UART_NLDEVS (ARRAY_SIZE(uart_lres)) +enum uart_be_type { + UART_BE_INVALID = 0, + UART_BE_STDIO, + UART_BE_TTY, + UART_BE_SOCK +}; + struct fifo { - uint8_t buf[FIFOSZ]; + uint8_t *buf; int rindex; /* index to read from */ int windex; /* index to write to */ int num; /* number of characters in the fifo */ int size; /* size of the fifo */ }; -struct ttyfd { - bool opened; - int fd_in; /* tty device file descriptor */ - int fd_out; /* stdin=0 stdout=1 should be different, when use stdio*/ - struct termios tio_orig, tio_new; /* I/O Terminals */ +struct uart_backend { + /* + * UART_BE_STDIO: fd = STDIN_FILENO + * UART_BE_TTY: fd = open(tty) + * UART_BE_SOCK: fd = file descriptor of listen socket + */ + int fd; + struct mevent *evp; + + /* + * UART_BE_STDIO: fd2 = STDOUT_FILENO + * UART_BE_TTY: fd2 = fd = open(tty) + * UART_BE_SOCK: fd2 = file descriptor of connected socket + */ + int fd2; + struct mevent *evp2; + + enum uart_be_type be_type; + bool opened; }; struct uart_vdev { @@ -108,93 +138,43 @@ struct uart_vdev { uint8_t dlh; /* Baudrate divisor latch MSB */ struct fifo rxfifo; - struct mevent *mev; + struct uart_backend be; - struct ttyfd tty; bool thre_int_pending; /* THRE interrupt pending */ void *arg; + int rxfifo_size; uart_intr_func_t intr_assert; uart_intr_func_t intr_deassert; }; static void uart_drain(int fd, enum ev_type ev, void *arg); static void uart_deinit(struct uart_vdev *uart); +static int uart_backend_read(struct uart_backend *be); +static int uart_backend_write(struct uart_backend *be, unsigned char wb); +static int uart_reset_backend(struct uart_backend *be); +static int uart_enable_backend(struct uart_backend *be, bool enable); static void -ttyclose(void) +uart_reset_stdio(void) { tcsetattr(STDIN_FILENO, TCSANOW, &tio_stdio_orig); -} - -static void -ttyopen(struct ttyfd *tf) -{ - tcgetattr(tf->fd_in, &tf->tio_orig); - - tf->tio_new = tf->tio_orig; - cfmakeraw(&tf->tio_new); - tf->tio_new.c_cflag |= CLOCAL; - tcflush(tf->fd_in, TCIOFLUSH); - tcsetattr(tf->fd_in, TCSANOW, &tf->tio_new); - - if (tf->fd_in == STDIN_FILENO) { - tio_stdio_orig = tf->tio_orig; - atexit(ttyclose); - } -} - -static int -ttyread(struct ttyfd *tf) -{ - unsigned char rb; - - if (read(tf->fd_in, &rb, 1) > 0) - return rb; - - return -1; -} - -static int -ttywrite(struct ttyfd *tf, unsigned char wb) -{ - if (write(tf->fd_out, &wb, 1) > 0) - return 1; - - return -1; + stdio_in_use = false; } static void rxfifo_reset(struct uart_vdev *uart, int size) { - char flushbuf[32]; struct fifo *fifo; - ssize_t nread; - int error; + assert(size <= uart->rxfifo_size); fifo = &uart->rxfifo; - bzero(fifo, sizeof(struct fifo)); + fifo->rindex = 0; + fifo->windex = 0; + fifo->num = 0; fifo->size = size; - if (uart->tty.opened) { - /* - * Flush any unread input from the tty buffer. - */ - while (1) { - nread = read(uart->tty.fd_in, flushbuf, sizeof(flushbuf)); - if (nread != sizeof(flushbuf)) - break; - } - - /* - * Enable mevent to trigger when new characters are available - * on the tty fd. - */ - if (isatty(uart->tty.fd_in)) { - error = mevent_enable(uart->mev); - assert(error == 0); - } - } + uart_reset_backend(&uart->be); } static int @@ -210,7 +190,6 @@ static int rxfifo_putchar(struct uart_vdev *uart, uint8_t ch) { struct fifo *fifo; - int error; fifo = &uart->rxfifo; @@ -218,17 +197,8 @@ rxfifo_putchar(struct uart_vdev *uart, uint8_t ch) fifo->buf[fifo->windex] = ch; fifo->windex = (fifo->windex + 1) % fifo->size; fifo->num++; - if (!rxfifo_available(uart)) { - if (uart->tty.opened) { - /* - * Disable mevent callback if the FIFO is full. - */ - if (isatty(uart->tty.fd_in)) { - error = mevent_disable(uart->mev); - assert(error == 0); - } - } - } + if (!rxfifo_available(uart)) + uart_enable_backend(&uart->be, false); return 0; } else return -1; @@ -238,7 +208,7 @@ static int rxfifo_getchar(struct uart_vdev *uart) { struct fifo *fifo; - int c, error, wasfull; + int c, wasfull; wasfull = 0; fifo = &uart->rxfifo; @@ -248,12 +218,8 @@ rxfifo_getchar(struct uart_vdev *uart) c = fifo->buf[fifo->rindex]; fifo->rindex = (fifo->rindex + 1) % fifo->size; fifo->num--; - if (wasfull) { - if (uart->tty.opened && isatty(uart->tty.fd_in)) { - error = mevent_enable(uart->mev); - assert(error == 0); - } - } + if (wasfull) + uart_enable_backend(&uart->be, true); return c; } else return -1; @@ -271,48 +237,44 @@ static void uart_mevent_teardown(void *param) { struct uart_vdev *uart = param; + struct uart_backend *be; - uart->mev = 0; - ttyclose(); + if (!uart) + return; - if (uart->tty.fd_in == STDIN_FILENO) { - stdio_in_use = false; - } else { - close(uart->tty.fd_in); + be = &uart->be; + + if (!be->opened) + return; + + switch (be->be_type) { + case UART_BE_STDIO: + uart_reset_stdio(); + break; + case UART_BE_TTY: + if (be->fd > 0) + close(be->fd); + break; + case UART_BE_SOCK: + if (be->fd2 > 0) + close(be->fd2); + if (be->fd > 0) + close(be->fd); + break; + default: + break; /* nothing to do */ } - uart->tty.fd_in = -1; - uart->tty.fd_out = -1; - uart->tty.opened = false; + be->evp = NULL; + be->evp2 = NULL; + be->fd2 = -1; + be->fd = -1; + be->opened = false; + be->be_type = UART_BE_INVALID; uart_deinit(uart); } -static void -uart_opentty(struct uart_vdev *uart) -{ - ttyopen(&uart->tty); - if (isatty(uart->tty.fd_in)) { - uart->mev = mevent_add(uart->tty.fd_in, EVF_READ, - uart_drain, uart, uart_mevent_teardown, uart); - assert(uart->mev != NULL); - } -} - -static void -uart_closetty(struct uart_vdev *uart) -{ - if (isatty(uart->tty.fd_in)) { - if (uart->tty.fd_in != STDIN_FILENO) - mevent_delete_close(uart->mev); - else - mevent_delete(uart->mev); - /* uart deinit will be invoked in mevent teardown callback */ - } else { - uart_mevent_teardown(uart); - } -} - static uint8_t modem_status(uint8_t mcr) { @@ -411,7 +373,6 @@ uart_drain(int fd, enum ev_type ev, void *arg) uart = arg; - assert(fd == uart->tty.fd_in); assert(ev == EVF_READ); /* @@ -422,12 +383,12 @@ uart_drain(int fd, enum ev_type ev, void *arg) pthread_mutex_lock(&uart->mtx); if ((uart->mcr & MCR_LOOPBACK) != 0) { - (void) ttyread(&uart->tty); + (void) uart_backend_read(&uart->be); } else { /* only read tty when rxfifo available to make sure no data lost */ - while (rxfifo_available(uart) && (ch = ttyread(&uart->tty)) != -1) { + while (rxfifo_available(uart) && (ch = uart_backend_read(&uart->be)) != -1) rxfifo_putchar(uart, ch); - } + uart_toggle_intr(uart); } @@ -465,8 +426,8 @@ uart_write(struct uart_vdev *uart, int offset, uint8_t value) if (uart->mcr & MCR_LOOPBACK) { if (rxfifo_putchar(uart, value) != 0) uart->lsr |= LSR_OE; - } else if (uart->tty.opened) { - ttywrite(&uart->tty, value); + } else { + uart_backend_write(&uart->be, value); } /* else drop on floor */ /* We view the transmission is completed immediately */ @@ -485,7 +446,7 @@ uart_write(struct uart_vdev *uart, int offset, uint8_t value) * the FIFO contents are reset. */ if ((uart->fcr & FCR_ENABLE) ^ (value & FCR_ENABLE)) { - fifosz = (value & FCR_ENABLE) ? FIFOSZ : 1; + fifosz = (value & FCR_ENABLE) ? uart->rxfifo_size : 1; rxfifo_reset(uart, fifosz); } @@ -497,7 +458,7 @@ uart_write(struct uart_vdev *uart, int offset, uint8_t value) uart->fcr = 0; } else { if ((value & FCR_RCV_RST) != 0) - rxfifo_reset(uart, FIFOSZ); + rxfifo_reset(uart, uart->rxfifo_size); uart->fcr = value & (FCR_ENABLE | FCR_DMA | FCR_RX_MASK); @@ -667,17 +628,19 @@ uart_legacy_dealloc(int which) static struct uart_vdev * uart_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert, - void *arg) + void *arg, int rxfifo_size) { struct uart_vdev *uart; - uart = calloc(1, sizeof(struct uart_vdev)); + uart = calloc(1, sizeof(struct uart_vdev) + rxfifo_size); assert(uart != NULL); uart->arg = arg; + uart->rxfifo_size = rxfifo_size; uart->intr_assert = intr_assert; uart->intr_deassert = intr_deassert; + uart->rxfifo.buf = (uint8_t *)(uart + 1); pthread_mutex_init(&uart->mtx, NULL); @@ -689,32 +652,302 @@ uart_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert, static void uart_deinit(struct uart_vdev *uart) { - if (uart) { - if (uart->tty.opened && uart->tty.fd_in == STDIN_FILENO) { - ttyclose(); - stdio_in_use = false; - } + if (uart) free(uart); +} + +static void +uart_sock_accept(int fd __attribute__((unused)), + enum ev_type t __attribute__((unused)), + void *arg) +{ + struct uart_vdev *uart = (struct uart_vdev *)arg; + int s, flags; + + s = accept(uart->be.fd, NULL, NULL); + if (s < 0) { + DPRINTF(("uart: accept error %d\n", s)); + return; } + + if (uart->be.opened) { + DPRINTF(("uart: already connected\n")); + close(s); + return; + } + + flags = fcntl(s, F_GETFL); + fcntl(s, F_SETFL, flags | O_NONBLOCK); + + uart->be.opened = true; + uart->be.fd2 = s; + uart->be.evp2 = mevent_add(s, EVF_READ, uart_drain, uart, + uart_mevent_teardown, uart); + if (!uart->be.evp2) + WPRINTF(("uart: mevent_add evp2 failed\n")); + DPRINTF(("uart: %s\r\n", __func__)); } static int -uart_tty_backend(struct uart_vdev *uart, const char *opts) +uart_backend_read(struct uart_backend *be) { - int fd; - int retval; + unsigned char rb; + int rc = -1; - retval = -1; + if (!be || !be->opened) + return -1; - fd = open(opts, O_RDWR | O_NONBLOCK); - if (fd > 0 && isatty(fd)) { - uart->tty.fd_in = fd; - uart->tty.fd_out = fd; - uart->tty.opened = true; - retval = 0; + switch (be->be_type) { + case UART_BE_STDIO: + case UART_BE_TTY: + /* fd is used to read */ + rc = read(be->fd, &rb, 1); + break; + case UART_BE_SOCK: + rc = recv(be->fd2, &rb, 1, 0); + if (rc <= 0 && errno != EAGAIN) { + if (be->evp2) { + mevent_delete(be->evp2); + be->evp2 = NULL; + } + if (be->fd2 > 0) { + close(be->fd2); + be->fd2 = -1; + } + be->opened = false; + WPRINTF(("%s connection closed, rc = %d, errno = %d\n", + __func__, rc, errno)); + } + break; + default: + WPRINTF(("not supported backend %d!\n", be->be_type)); } - return retval; + if (rc <= 0) + return -1; + + return rb; +} + +static int +uart_backend_write(struct uart_backend *be, unsigned char wb) +{ + int rc = -1; + + if (!be || !be->opened) + return -1; + + switch (be->be_type) { + case UART_BE_STDIO: + case UART_BE_TTY: + /* fd2 is used to write */ + rc = write(be->fd2, &wb, 1); + break; + case UART_BE_SOCK: + rc = send(be->fd2, &wb, 1, 0); + if (rc != 1) + WPRINTF(("%s: send error, rc = %d, errno = %d\r\n", + __func__, rc, errno)); + break; + default: + WPRINTF(("not supported backend %d!\n", be->be_type)); + } + + return rc; +} + +static int +uart_reset_backend(struct uart_backend *be) +{ + char flushbuf[32]; + ssize_t nread; + int error; + int fd; + struct mevent *evp; + + if (!be || !be->opened) + return -1; + + switch (be->be_type) { + case UART_BE_STDIO: + case UART_BE_TTY: + fd = be->fd; + evp = be->evp; + break; + case UART_BE_SOCK: + fd = be->fd2; + evp = be->evp2; + break; + default: + WPRINTF(("not supported backend %d!\n", be->be_type)); + return -1; + } + + /* Flush any unread input from the backend device. */ + while (1) { + nread = read(fd, flushbuf, sizeof(flushbuf)); + if (nread != sizeof(flushbuf)) + break; + } + + error = mevent_enable(evp); + if (error) { + WPRINTF(("mevent_enable error\n")); + return -1; + } + + return 0; +} + +static int +uart_enable_backend(struct uart_backend *be, bool enable) +{ + int error; + struct mevent *evp; + + if (!be || !be->opened) + return -1; + + switch (be->be_type) { + case UART_BE_STDIO: + case UART_BE_TTY: + evp = be->evp; + break; + case UART_BE_SOCK: + evp = be->evp2; + break; + default: + WPRINTF(("not supported backend %d!\n", be->be_type)); + return -1; + } + + if (enable) + error = mevent_enable(evp); + else + error = mevent_disable(evp); + if (error) { + WPRINTF(("mevent %s error\n", enable ? "enable" : "disable")); + return -1; + } + + return 0; +} + +static int +uart_open_backend(struct uart_backend *be, const char *path, + enum uart_be_type be_type) +{ + int fd, rc = -1; + + switch (be_type) { + case UART_BE_STDIO: + if (stdio_in_use) { + WPRINTF(("uart: stdio is used by other device\n")); + break; + } + be->fd = STDIN_FILENO; + be->fd2 = STDOUT_FILENO; + stdio_in_use = true; + rc = 0; + break; + case UART_BE_TTY: + fd = open(path, O_RDWR | O_NONBLOCK); + if (fd < 0) + WPRINTF(("uart: open failed: %s\n", path)); + else if (!isatty(fd)) { + WPRINTF(("uart: not a tty: %s\n", path)); + close(fd); + fd = -1; + } else { + be->fd = fd; + be->fd2 = fd; + rc = 0; + } + break; + case UART_BE_SOCK: + fd = socket(AF_INET, SOCK_STREAM | O_NONBLOCK, 0); + if (fd < 0) + WPRINTF(("uart: open socket failed\n")); + else { + be->fd = fd; + rc = 0; + } + break; + default: + WPRINTF(("not supported backend %d!\n", be_type)); + } + + return rc; +} + +static int +uart_config_backend(struct uart_vdev *uart, struct uart_backend *be, long port) +{ + int fd, flags; + struct termios tio, saved_tio; + int opt = true; + struct sockaddr_in addr; + + if (!be || be->fd == -1) + return -1; + + fd = be->fd; + switch (be->be_type) { + case UART_BE_TTY: + case UART_BE_STDIO: + tcgetattr(fd, &tio); + saved_tio = tio; + cfmakeraw(&tio); + tio.c_cflag |= CLOCAL; + tcflush(fd, TCIOFLUSH); + tcsetattr(fd, TCSANOW, &tio); + + if (be->be_type == UART_BE_STDIO) { + flags = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + tio_stdio_orig = saved_tio; + atexit(uart_reset_stdio); + } + be->opened = true; + be->evp = mevent_add(fd, EVF_READ, uart_drain, uart, + uart_mevent_teardown, uart); + if (!be->evp) { + WPRINTF(("uart: mevent_add failed\n")); + return -1; + } + break; + case UART_BE_SOCK: + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, + sizeof(opt)) < 0) { + WPRINTF(("uart: setsockopt failed, errno = %d\n", + errno)); + return -1; + } + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + WPRINTF(("uart: bind failed, errno = %d\n", + errno)); + return -1; + } + if (listen(fd, 1) < 0) { + WPRINTF(("uart: listen failed, errno = %d\n", + errno)); + return -1; + } + be->opened = false; + be->evp = mevent_add(fd, EVF_READ, uart_sock_accept, uart, NULL, NULL); + if (!be->evp) { + WPRINTF(("uart: mevent_add failed\n")); + return -1; + } + break; + default: + break; /* nothing to do */ + } + + return 0; } struct uart_vdev * @@ -723,56 +956,95 @@ uart_set_backend(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert, { int retval = -1; struct uart_vdev *uart; + struct uart_backend *be = NULL; + const char *path = NULL; + enum uart_be_type be_type = UART_BE_INVALID; + char *vopts, *p; + long port = 0; + int rxfifo_size = DEFAULT_FIFOSZ; - uart = uart_init(intr_assert, intr_deassert, arg); - if (!uart) - return NULL; - - if (opts == NULL) + if (opts == NULL) { + uart = uart_init(intr_assert, intr_deassert, arg, + rxfifo_size); return uart; + } - if (strcmp("stdio", opts) == 0) { - if (!stdio_in_use) { - uart->tty.fd_in = STDIN_FILENO; - uart->tty.fd_out = STDOUT_FILENO; - uart->tty.opened = true; - stdio_in_use = true; - retval = 0; + if (strncmp(opts, "tcp", 3) == 0) { + be_type = UART_BE_SOCK; + rxfifo_size = SOCK_FIFOSZ; + vopts = strdup(opts); + if (!vopts) + goto opts_fail; + + p = vopts; + if (!strsep(&p, ":") || dm_strtol(p, &p, 10, &port)) { + free(vopts); + goto opts_fail; } - } else if (uart_tty_backend(uart, opts) == 0) { - retval = 0; + + free(vopts); + vopts = NULL; + } else if (strcmp("stdio", opts) == 0) { + be_type = UART_BE_STDIO; + } else { + be_type = UART_BE_TTY; + path = opts; } - if (retval) { - uart_deinit(uart); - return NULL; + uart = uart_init(intr_assert, intr_deassert, arg, rxfifo_size); + if (!uart) + goto init_fail; + + be = &uart->be; + retval = uart_open_backend(be, path, be_type); + if (retval < 0) { + WPRINTF(("uart: open_backend failed\n")); + goto open_fail; } - /* Make the backend file descriptor non-blocking */ - retval = fcntl(uart->tty.fd_in, F_SETFL, O_NONBLOCK); - if (retval != 0) - goto fail_tty_backend; + be->be_type = be_type; + if (uart_config_backend(uart, be, port) < 0) { + WPRINTF(("uart: config_backend failed\n")); + goto config_fail; + } - uart_opentty(uart); return uart; -fail_tty_backend: - if (uart->tty.fd_in) { - close(uart->tty.fd_in); - uart->tty.fd_in = -1; - uart->tty.fd_out = -1; - uart->tty.opened = false; - } +config_fail: + /* for all kinds of be, be->evp2 is not initialized */ + if (be->be_type == UART_BE_SOCK) { + /* there is no teardown callback for socket listen fd */ + if (be->evp) { + mevent_delete(be->evp); + be->evp = NULL; + } + if (be->fd > 0) { + close(be->fd); + be->fd = -1; + } + } else if (be->evp) + mevent_delete(be->evp); + else + uart_mevent_teardown(uart); + return NULL; + +open_fail: uart_deinit(uart); +init_fail: +opts_fail: return NULL; } void uart_release_backend(struct uart_vdev *uart, const char *opts) { - if (opts == NULL) + struct uart_backend *be; + + if (uart == NULL) return; + be = &uart->be; + /* * By current design, for the invalid PTY parameters, the virtual uarts * are still expose to UOS but all data be dropped by backend service. @@ -780,8 +1052,29 @@ uart_release_backend(struct uart_vdev *uart, const char *opts) * the uart backend in here. * TODO: need re-visit the whole policy for such scenario in future. */ - if (!uart->tty.opened) + if (opts == NULL || be->be_type == UART_BE_INVALID) { + uart_deinit(uart); return; + } - uart_closetty(uart); + if (be->be_type == UART_BE_SOCK) { + /* there is no teardown callback for socket listen fd */ + if (be->evp) { + mevent_delete(be->evp); + be->evp = NULL; + } + if (be->fd > 0) { + close(be->fd); + be->fd = -1; + } + if (be->evp2) + mevent_delete(be->evp2); + else + uart_mevent_teardown(uart); + } else { + if (be->evp) + mevent_delete(be->evp); + else + uart_mevent_teardown(uart); + } }