aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/bhyve/uart_backend.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bhyve/uart_backend.c')
-rw-r--r--usr.sbin/bhyve/uart_backend.c561
1 files changed, 561 insertions, 0 deletions
diff --git a/usr.sbin/bhyve/uart_backend.c b/usr.sbin/bhyve/uart_backend.c
new file mode 100644
index 000000000000..32490105fecf
--- /dev/null
+++ b/usr.sbin/bhyve/uart_backend.c
@@ -0,0 +1,561 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 NetApp, Inc.
+ * Copyright (c) 2013 Neel Natu <neel@freebsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <machine/vmm.h>
+#include <machine/vmm_snapshot.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <capsicum_helpers.h>
+#include <err.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "mevent.h"
+#include "uart_backend.h"
+
+struct ttyfd {
+ bool opened;
+ bool is_socket;
+ int rfd; /* fd for reading */
+ int wfd; /* fd for writing, may be == rfd */
+};
+
+#define FIFOSZ 16
+
+struct fifo {
+ uint8_t buf[FIFOSZ];
+ 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 uart_softc {
+ struct ttyfd tty;
+ struct fifo rxfifo;
+ struct mevent *mev;
+ pthread_mutex_t mtx;
+};
+
+struct uart_socket_softc {
+ struct uart_softc *softc;
+ void (*drain)(int, enum ev_type, void *);
+ void *arg;
+};
+
+static bool uart_stdio; /* stdio in use for i/o */
+static struct termios tio_stdio_orig;
+
+static void uart_tcp_disconnect(struct uart_softc *);
+
+static void
+ttyclose(void)
+{
+ tcsetattr(STDIN_FILENO, TCSANOW, &tio_stdio_orig);
+}
+
+static void
+ttyopen(struct ttyfd *tf)
+{
+ struct termios orig, new;
+
+ tcgetattr(tf->rfd, &orig);
+ new = orig;
+ cfmakeraw(&new);
+ new.c_cflag |= CLOCAL;
+ tcsetattr(tf->rfd, TCSANOW, &new);
+ if (uart_stdio) {
+ tio_stdio_orig = orig;
+ atexit(ttyclose);
+ }
+ raw_stdio = 1;
+}
+
+static int
+ttyread(struct ttyfd *tf, uint8_t *ret)
+{
+ uint8_t rb;
+ int len;
+
+ len = read(tf->rfd, &rb, 1);
+ if (ret && len == 1)
+ *ret = rb;
+
+ return (len);
+}
+
+static int
+ttywrite(struct ttyfd *tf, unsigned char wb)
+{
+ return (write(tf->wfd, &wb, 1));
+}
+
+static bool
+rxfifo_available(struct uart_softc *sc)
+{
+ return (sc->rxfifo.num < sc->rxfifo.size);
+}
+
+int
+uart_rxfifo_getchar(struct uart_softc *sc)
+{
+ struct fifo *fifo;
+ int c, error, wasfull;
+
+ wasfull = 0;
+ fifo = &sc->rxfifo;
+ if (fifo->num > 0) {
+ if (!rxfifo_available(sc))
+ wasfull = 1;
+ c = fifo->buf[fifo->rindex];
+ fifo->rindex = (fifo->rindex + 1) % fifo->size;
+ fifo->num--;
+ if (wasfull) {
+ if (sc->tty.opened) {
+ error = mevent_enable(sc->mev);
+ assert(error == 0);
+ }
+ }
+ return (c);
+ } else
+ return (-1);
+}
+
+int
+uart_rxfifo_numchars(struct uart_softc *sc)
+{
+ return (sc->rxfifo.num);
+}
+
+static int
+rxfifo_putchar(struct uart_softc *sc, uint8_t ch)
+{
+ struct fifo *fifo;
+ int error;
+
+ fifo = &sc->rxfifo;
+
+ if (fifo->num < fifo->size) {
+ fifo->buf[fifo->windex] = ch;
+ fifo->windex = (fifo->windex + 1) % fifo->size;
+ fifo->num++;
+ if (!rxfifo_available(sc)) {
+ if (sc->tty.opened) {
+ /*
+ * Disable mevent callback if the FIFO is full.
+ */
+ error = mevent_disable(sc->mev);
+ assert(error == 0);
+ }
+ }
+ return (0);
+ } else
+ return (-1);
+}
+
+void
+uart_rxfifo_drain(struct uart_softc *sc, bool loopback)
+{
+ uint8_t ch;
+ int len;
+
+ if (loopback) {
+ if (ttyread(&sc->tty, &ch) == 0 && sc->tty.is_socket)
+ uart_tcp_disconnect(sc);
+ } else {
+ while (rxfifo_available(sc)) {
+ len = ttyread(&sc->tty, &ch);
+ if (len <= 0) {
+ /* read returning 0 means disconnected. */
+ if (len == 0 && sc->tty.is_socket)
+ uart_tcp_disconnect(sc);
+ break;
+ }
+
+ rxfifo_putchar(sc, ch);
+ }
+ }
+}
+
+int
+uart_rxfifo_putchar(struct uart_softc *sc, uint8_t ch, bool loopback)
+{
+ if (loopback) {
+ return (rxfifo_putchar(sc, ch));
+ } else if (sc->tty.opened) {
+ /* write returning -1 means disconnected. */
+ if (ttywrite(&sc->tty, ch) == -1 && sc->tty.is_socket)
+ uart_tcp_disconnect(sc);
+ return (0);
+ } else {
+ /* Drop on the floor. */
+ return (0);
+ }
+}
+
+void
+uart_rxfifo_reset(struct uart_softc *sc, int size)
+{
+ char flushbuf[32];
+ struct fifo *fifo;
+ ssize_t nread;
+ int error;
+
+ fifo = &sc->rxfifo;
+ bzero(fifo, sizeof(struct fifo));
+ fifo->size = size;
+
+ if (sc->tty.opened) {
+ /*
+ * Flush any unread input from the tty buffer.
+ */
+ while (1) {
+ nread = read(sc->tty.rfd, flushbuf, sizeof(flushbuf));
+ if (nread != sizeof(flushbuf))
+ break;
+ }
+
+ /*
+ * Enable mevent to trigger when new characters are available
+ * on the tty fd.
+ */
+ error = mevent_enable(sc->mev);
+ assert(error == 0);
+ }
+}
+
+int
+uart_rxfifo_size(struct uart_softc *sc __unused)
+{
+ return (FIFOSZ);
+}
+
+#ifdef BHYVE_SNAPSHOT
+int
+uart_rxfifo_snapshot(struct uart_softc *sc, struct vm_snapshot_meta *meta)
+{
+ int ret;
+
+ SNAPSHOT_VAR_OR_LEAVE(sc->rxfifo.rindex, meta, ret, done);
+ SNAPSHOT_VAR_OR_LEAVE(sc->rxfifo.windex, meta, ret, done);
+ SNAPSHOT_VAR_OR_LEAVE(sc->rxfifo.num, meta, ret, done);
+ SNAPSHOT_VAR_OR_LEAVE(sc->rxfifo.size, meta, ret, done);
+ SNAPSHOT_BUF_OR_LEAVE(sc->rxfifo.buf, sizeof(sc->rxfifo.buf),
+ meta, ret, done);
+
+done:
+ return (ret);
+}
+#endif
+
+/*
+ * Listen on the TCP port, wait for a connection, then accept it.
+ */
+static void
+uart_tcp_listener(int fd, enum ev_type type __unused, void *arg)
+{
+ static const char tcp_error_msg[] = "Socket already connected\n";
+ struct uart_socket_softc *socket_softc = (struct uart_socket_softc *)
+ arg;
+ struct uart_softc *sc = socket_softc->softc;
+ int conn_fd;
+
+ conn_fd = accept(fd, NULL, NULL);
+ if (conn_fd == -1)
+ goto clean;
+
+ if (fcntl(conn_fd, F_SETFL, O_NONBLOCK) != 0)
+ goto clean;
+
+ pthread_mutex_lock(&sc->mtx);
+
+ if (sc->tty.opened) {
+ (void)send(conn_fd, tcp_error_msg, sizeof(tcp_error_msg), 0);
+ pthread_mutex_unlock(&sc->mtx);
+ goto clean;
+ } else {
+ sc->tty.rfd = sc->tty.wfd = conn_fd;
+ sc->tty.opened = true;
+ sc->mev = mevent_add(sc->tty.rfd, EVF_READ, socket_softc->drain,
+ socket_softc->arg);
+ }
+
+ pthread_mutex_unlock(&sc->mtx);
+ return;
+
+clean:
+ if (conn_fd != -1)
+ close(conn_fd);
+}
+
+/*
+ * When a connection-oriented protocol disconnects, this handler is used to
+ * clean it up.
+ *
+ * Note that this function is a helper, so the caller is responsible for
+ * locking the softc.
+ */
+static void
+uart_tcp_disconnect(struct uart_softc *sc)
+{
+ mevent_delete_close(sc->mev);
+ sc->mev = NULL;
+ sc->tty.opened = false;
+ sc->tty.rfd = sc->tty.wfd = -1;
+}
+
+static int
+uart_stdio_backend(struct uart_softc *sc)
+{
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_t rights;
+ cap_ioctl_t cmds[] = { TIOCGETA, TIOCSETA, TIOCGWINSZ };
+#endif
+
+ if (uart_stdio)
+ return (-1);
+
+ sc->tty.rfd = STDIN_FILENO;
+ sc->tty.wfd = STDOUT_FILENO;
+ sc->tty.opened = true;
+
+ if (fcntl(sc->tty.rfd, F_SETFL, O_NONBLOCK) != 0)
+ return (-1);
+ if (fcntl(sc->tty.wfd, F_SETFL, O_NONBLOCK) != 0)
+ return (-1);
+
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_init(&rights, CAP_EVENT, CAP_IOCTL, CAP_READ);
+ if (caph_rights_limit(sc->tty.rfd, &rights) == -1)
+ errx(EX_OSERR, "Unable to apply rights for sandbox");
+ if (caph_ioctls_limit(sc->tty.rfd, cmds, nitems(cmds)) == -1)
+ errx(EX_OSERR, "Unable to apply rights for sandbox");
+#endif
+
+ uart_stdio = true;
+
+ return (0);
+}
+
+static int
+uart_tty_backend(struct uart_softc *sc, const char *path)
+{
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_t rights;
+ cap_ioctl_t cmds[] = { TIOCGETA, TIOCSETA, TIOCGWINSZ };
+#endif
+ int fd;
+
+ fd = open(path, O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ return (-1);
+
+ if (!isatty(fd)) {
+ close(fd);
+ return (-1);
+ }
+
+ sc->tty.rfd = sc->tty.wfd = fd;
+ sc->tty.opened = true;
+
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_init(&rights, CAP_EVENT, CAP_IOCTL, CAP_READ, CAP_WRITE);
+ if (caph_rights_limit(fd, &rights) == -1)
+ errx(EX_OSERR, "Unable to apply rights for sandbox");
+ if (caph_ioctls_limit(fd, cmds, nitems(cmds)) == -1)
+ errx(EX_OSERR, "Unable to apply rights for sandbox");
+#endif
+
+ return (0);
+}
+
+/*
+ * Listen on the address and add it to the kqueue.
+ *
+ * If a connection is established (e.g., the TCP handler is triggered),
+ * replace the handler with the connected handler.
+ */
+static int
+uart_tcp_backend(struct uart_softc *sc, const char *path,
+ void (*drain)(int, enum ev_type, void *), void *arg)
+{
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_t rights;
+ cap_ioctl_t cmds[] = { TIOCGETA, TIOCSETA, TIOCGWINSZ };
+#endif
+ int bind_fd = -1;
+ char addr[256], port[6];
+ int domain;
+ struct addrinfo hints, *src_addr = NULL;
+ struct uart_socket_softc *socket_softc = NULL;
+
+ if (sscanf(path, "tcp=[%255[^]]]:%5s", addr, port) == 2) {
+ domain = AF_INET6;
+ } else if (sscanf(path, "tcp=%255[^:]:%5s", addr, port) == 2) {
+ domain = AF_INET;
+ } else {
+ warnx("Invalid number of parameter");
+ goto clean;
+ }
+
+ bind_fd = socket(domain, SOCK_STREAM, 0);
+ if (bind_fd < 0)
+ goto clean;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = domain;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
+
+ if (getaddrinfo(addr, port, &hints, &src_addr) != 0) {
+ warnx("Invalid address %s:%s", addr, port);
+ goto clean;
+ }
+
+ if (bind(bind_fd, src_addr->ai_addr, src_addr->ai_addrlen) == -1) {
+ warn(
+ "bind(%s:%s)",
+ addr, port);
+ goto clean;
+ }
+
+ freeaddrinfo(src_addr);
+ src_addr = NULL;
+
+ if (fcntl(bind_fd, F_SETFL, O_NONBLOCK) == -1)
+ goto clean;
+
+ if (listen(bind_fd, 1) == -1) {
+ warnx("listen(%s:%s)", addr, port);
+ goto clean;
+ }
+
+ /*
+ * Set the connection softc structure, which includes both the softc
+ * and the drain function provided by the frontend.
+ */
+ if ((socket_softc = calloc(1, sizeof(struct uart_socket_softc))) ==
+ NULL)
+ goto clean;
+
+ sc->tty.is_socket = true;
+
+ socket_softc->softc = sc;
+ socket_softc->drain = drain;
+ socket_softc->arg = arg;
+
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_init(&rights, CAP_EVENT, CAP_ACCEPT, CAP_RECV, CAP_SEND,
+ CAP_FCNTL, CAP_IOCTL);
+ if (caph_rights_limit(bind_fd, &rights) == -1)
+ errx(EX_OSERR, "Unable to apply rights for sandbox");
+ if (caph_ioctls_limit(bind_fd, cmds, nitems(cmds)) == -1)
+ errx(EX_OSERR, "Unable to apply ioctls for sandbox");
+ if (caph_fcntls_limit(bind_fd, CAP_FCNTL_SETFL) == -1)
+ errx(EX_OSERR, "Unable to apply fcntls for sandbox");
+#endif
+
+ if ((sc->mev = mevent_add(bind_fd, EVF_READ, uart_tcp_listener,
+ socket_softc)) == NULL)
+ goto clean;
+
+ return (0);
+
+clean:
+ if (bind_fd != -1)
+ close(bind_fd);
+ if (socket_softc != NULL)
+ free(socket_softc);
+ if (src_addr)
+ freeaddrinfo(src_addr);
+ return (-1);
+}
+
+struct uart_softc *
+uart_init(void)
+{
+ struct uart_softc *sc = calloc(1, sizeof(struct uart_softc));
+ if (sc == NULL)
+ return (NULL);
+
+ pthread_mutex_init(&sc->mtx, NULL);
+
+ return (sc);
+}
+
+int
+uart_tty_open(struct uart_softc *sc, const char *path,
+ void (*drain)(int, enum ev_type, void *), void *arg)
+{
+ int retval;
+
+ if (strcmp("stdio", path) == 0)
+ retval = uart_stdio_backend(sc);
+ else if (strncmp("tcp", path, 3) == 0)
+ retval = uart_tcp_backend(sc, path, drain, arg);
+ else
+ retval = uart_tty_backend(sc, path);
+
+ /*
+ * A connection-oriented protocol should wait for a connection,
+ * so it may not listen to anything during initialization.
+ */
+ if (retval == 0 && !sc->tty.is_socket) {
+ ttyopen(&sc->tty);
+ sc->mev = mevent_add(sc->tty.rfd, EVF_READ, drain, arg);
+ assert(sc->mev != NULL);
+ }
+
+ return (retval);
+}
+
+void
+uart_softc_lock(struct uart_softc *sc)
+{
+ pthread_mutex_lock(&sc->mtx);
+}
+
+void
+uart_softc_unlock(struct uart_softc *sc)
+{
+ pthread_mutex_unlock(&sc->mtx);
+}