Allow bhyve and bhyveload to attach to tty devices.

bhyveload: introduce the -c <device> parameter
 to select a tty for output (or "stdio")

bhyve: allow the puc and lpc-com backends to
 accept a tty in addition to "stdio"

When used in conjunction with the null-modem device,
nmdm(4), this allows attach/detach to the guest console
and multiple concurrent serial ports. kgdb on a serial
port is now functional.

Reviewed by:	neel
Requested by:	Almost everyone that has used bhyve
MFC after:	10.0
This commit is contained in:
Peter Grehan 2013-11-27 00:21:37 +00:00
parent 0d8dc7cc39
commit 6380102c7f
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=258668
3 changed files with 149 additions and 58 deletions

View File

@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$");
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <fcntl.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <stdbool.h> #include <stdbool.h>
@ -67,6 +68,7 @@ __FBSDID("$FreeBSD$");
#define FIFOSZ 16 #define FIFOSZ 16
static bool uart_stdio; /* stdio in use for i/o */ static bool uart_stdio; /* stdio in use for i/o */
static struct termios tio_stdio_orig;
static struct { static struct {
int baseaddr; int baseaddr;
@ -87,6 +89,12 @@ struct fifo {
int size; /* size of the fifo */ int size; /* size of the fifo */
}; };
struct ttyfd {
bool opened;
int fd; /* tty device file descriptor */
struct termios tio_orig, tio_new; /* I/O Terminals */
};
struct uart_softc { struct uart_softc {
pthread_mutex_t mtx; /* protects all softc elements */ pthread_mutex_t mtx; /* protects all softc elements */
uint8_t data; /* Data register (R/W) */ uint8_t data; /* Data register (R/W) */
@ -103,8 +111,7 @@ struct uart_softc {
struct fifo rxfifo; struct fifo rxfifo;
bool opened; struct ttyfd tty;
bool stdio;
bool thre_int_pending; /* THRE interrupt pending */ bool thre_int_pending; /* THRE interrupt pending */
void *arg; void *arg;
@ -114,38 +121,41 @@ struct uart_softc {
static void uart_drain(int fd, enum ev_type ev, void *arg); static void uart_drain(int fd, enum ev_type ev, void *arg);
static struct termios tio_orig, tio_new; /* I/O Terminals */
static void static void
ttyclose(void) ttyclose(void)
{ {
tcsetattr(STDIN_FILENO, TCSANOW, &tio_orig); tcsetattr(STDIN_FILENO, TCSANOW, &tio_stdio_orig);
} }
static void static void
ttyopen(void) ttyopen(struct ttyfd *tf)
{ {
tcgetattr(STDIN_FILENO, &tio_orig); tcgetattr(tf->fd, &tf->tio_orig);
cfmakeraw(&tio_new); tf->tio_new = tf->tio_orig;
tcsetattr(STDIN_FILENO, TCSANOW, &tio_new); cfmakeraw(&tf->tio_new);
tf->tio_new.c_cflag |= CLOCAL;
tcsetattr(tf->fd, TCSANOW, &tf->tio_new);
atexit(ttyclose); if (tf->fd == STDIN_FILENO) {
tio_stdio_orig = tf->tio_orig;
atexit(ttyclose);
}
} }
static bool static bool
tty_char_available(void) tty_char_available(struct ttyfd *tf)
{ {
fd_set rfds; fd_set rfds;
struct timeval tv; struct timeval tv;
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds); FD_SET(tf->fd, &rfds);
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 0; tv.tv_usec = 0;
if (select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv) > 0 ) { if (select(tf->fd + 1, &rfds, NULL, NULL, &tv) > 0 ) {
return (true); return (true);
} else { } else {
return (false); return (false);
@ -153,12 +163,12 @@ tty_char_available(void)
} }
static int static int
ttyread(void) ttyread(struct ttyfd *tf)
{ {
char rb; char rb;
if (tty_char_available()) { if (tty_char_available(tf)) {
read(STDIN_FILENO, &rb, 1); read(tf->fd, &rb, 1);
return (rb & 0xff); return (rb & 0xff);
} else { } else {
return (-1); return (-1);
@ -166,10 +176,10 @@ ttyread(void)
} }
static void static void
ttywrite(unsigned char wb) ttywrite(struct ttyfd *tf, unsigned char wb)
{ {
(void)write(STDIN_FILENO, &wb, 1); (void)write(tf->fd, &wb, 1);
} }
static void static void
@ -226,10 +236,8 @@ uart_opentty(struct uart_softc *sc)
{ {
struct mevent *mev; struct mevent *mev;
assert(!sc->opened && sc->stdio); ttyopen(&sc->tty);
mev = mevent_add(sc->tty.fd, EVF_READ, uart_drain, sc);
ttyopen();
mev = mevent_add(STDIN_FILENO, EVF_READ, uart_drain, sc);
assert(mev); assert(mev);
} }
@ -294,7 +302,7 @@ uart_drain(int fd, enum ev_type ev, void *arg)
sc = arg; sc = arg;
assert(fd == STDIN_FILENO); assert(fd == sc->tty.fd);
assert(ev == EVF_READ); assert(ev == EVF_READ);
/* /*
@ -305,10 +313,10 @@ uart_drain(int fd, enum ev_type ev, void *arg)
pthread_mutex_lock(&sc->mtx); pthread_mutex_lock(&sc->mtx);
if ((sc->mcr & MCR_LOOPBACK) != 0) { if ((sc->mcr & MCR_LOOPBACK) != 0) {
(void) ttyread(); (void) ttyread(&sc->tty);
} else { } else {
while (fifo_available(&sc->rxfifo) && while (fifo_available(&sc->rxfifo) &&
((ch = ttyread()) != -1)) { ((ch = ttyread(&sc->tty)) != -1)) {
fifo_putchar(&sc->rxfifo, ch); fifo_putchar(&sc->rxfifo, ch);
} }
uart_toggle_intr(sc); uart_toggle_intr(sc);
@ -323,12 +331,6 @@ uart_write(struct uart_softc *sc, int offset, uint8_t value)
int fifosz; int fifosz;
uint8_t msr; uint8_t msr;
/* Open terminal */
if (!sc->opened && sc->stdio) {
uart_opentty(sc);
sc->opened = true;
}
pthread_mutex_lock(&sc->mtx); pthread_mutex_lock(&sc->mtx);
/* /*
@ -351,8 +353,8 @@ uart_write(struct uart_softc *sc, int offset, uint8_t value)
if (sc->mcr & MCR_LOOPBACK) { if (sc->mcr & MCR_LOOPBACK) {
if (fifo_putchar(&sc->rxfifo, value) != 0) if (fifo_putchar(&sc->rxfifo, value) != 0)
sc->lsr |= LSR_OE; sc->lsr |= LSR_OE;
} else if (sc->stdio) { } else if (sc->tty.opened) {
ttywrite(value); ttywrite(&sc->tty, value);
} /* else drop on floor */ } /* else drop on floor */
sc->thre_int_pending = true; sc->thre_int_pending = true;
break; break;
@ -459,12 +461,6 @@ uart_read(struct uart_softc *sc, int offset)
{ {
uint8_t iir, intr_reason, reg; uint8_t iir, intr_reason, reg;
/* Open terminal */
if (!sc->opened && sc->stdio) {
uart_opentty(sc);
sc->opened = true;
}
pthread_mutex_lock(&sc->mtx); pthread_mutex_lock(&sc->mtx);
/* /*
@ -581,19 +577,47 @@ uart_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert,
return (sc); return (sc);
} }
static int
uart_tty_backend(struct uart_softc *sc, const char *opts)
{
int fd;
int retval;
retval = -1;
fd = open(opts, O_RDWR);
if (fd > 0 && isatty(fd)) {
sc->tty.fd = fd;
sc->tty.opened = true;
retval = 0;
}
return (retval);
}
int int
uart_set_backend(struct uart_softc *sc, const char *opts) uart_set_backend(struct uart_softc *sc, const char *opts)
{ {
/* int retval;
* XXX one stdio backend supported at this time.
*/ retval = -1;
if (opts == NULL) if (opts == NULL)
return (0); return (0);
if (strcmp("stdio", opts) == 0 && !uart_stdio) { if (strcmp("stdio", opts) == 0) {
sc->stdio = true; if (!uart_stdio) {
uart_stdio = true; sc->tty.fd = STDIN_FILENO;
return (0); sc->tty.opened = true;
} else uart_stdio = true;
return (-1); retval = 0;
}
} else if (uart_tty_backend(sc, opts) == 0) {
retval = 0;
}
if (retval == 0)
uart_opentty(sc);
return (retval);
} }

View File

@ -39,6 +39,7 @@ guest inside a bhyve virtual machine
.Op Fl d Ar disk-path .Op Fl d Ar disk-path
.Op Fl h Ar host-path .Op Fl h Ar host-path
.Op Fl e Ar name=value .Op Fl e Ar name=value
.Op Fl c Ar cons-dev
.Ar vmname .Ar vmname
.Sh DESCRIPTION .Sh DESCRIPTION
.Nm .Nm
@ -100,6 +101,16 @@ to
.Pp .Pp
The option may be used more than once to set more than one environment The option may be used more than once to set more than one environment
variable. variable.
.It Fl c Ar cons-dev
.Ar cons-dev
is a
.Xr tty 4
device to use for
.Nm
terminal I/O.
.Pp
The text string "stdio" is also accepted and selects the use of
unbuffered standard I/O. This is the default value.
.El .El
.Sh EXAMPLES .Sh EXAMPLES
To create a virtual machine named To create a virtual machine named
@ -109,10 +120,23 @@ that boots off the ISO image
and has 1GB memory allocated to it: and has 1GB memory allocated to it:
.Pp .Pp
.Dl "bhyveload -m 1G -d /freebsd/release.iso freebsd-vm" .Dl "bhyveload -m 1G -d /freebsd/release.iso freebsd-vm"
.Pp
To create a virtual machine named
.Ar test-vm
with 256MB of memory allocated, the guest root filesystem under the host
directory
.Pa /user/images/test
and terminal I/O sent to the
.Xr nmdm 4
device
.Pa /dev/nmdm1B
.Pp
.Dl "bhyveload -m 256MB -h /usr/images/test -c /dev/nmdm1B test-vm
.Sh SEE ALSO .Sh SEE ALSO
.Xr bhyve 4 , .Xr bhyve 4 ,
.Xr bhyve 8 , .Xr bhyve 8 ,
.Xr loader 8 , .Xr loader 8 ,
.Xr nmdm 4,
.Xr vmm 4 .Xr vmm 4
.Sh HISTORY .Sh HISTORY
.Nm .Nm

View File

@ -91,6 +91,7 @@ __FBSDID("$FreeBSD$");
static char *host_base = "/"; static char *host_base = "/";
static struct termios term, oldterm; static struct termios term, oldterm;
static int disk_fd = -1; static int disk_fd = -1;
static int consin_fd, consout_fd;
static char *vmname, *progname; static char *vmname, *progname;
static struct vmctx *ctx; static struct vmctx *ctx;
@ -108,7 +109,7 @@ cb_putc(void *arg, int ch)
{ {
char c = ch; char c = ch;
write(1, &c, 1); (void) write(consout_fd, &c, 1);
} }
static int static int
@ -116,7 +117,7 @@ cb_getc(void *arg)
{ {
char c; char c;
if (read(0, &c, 1) == 1) if (read(consin_fd, &c, 1) == 1)
return (c); return (c);
return (-1); return (-1);
} }
@ -126,7 +127,7 @@ cb_poll(void *arg)
{ {
int n; int n;
if (ioctl(0, FIONREAD, &n) >= 0) if (ioctl(consin_fd, FIONREAD, &n) >= 0)
return (n > 0); return (n > 0);
return (0); return (0);
} }
@ -488,7 +489,7 @@ static void
cb_exit(void *arg, int v) cb_exit(void *arg, int v)
{ {
tcsetattr(0, TCSAFLUSH, &oldterm); tcsetattr(consout_fd, TCSAFLUSH, &oldterm);
exit(v); exit(v);
} }
@ -564,13 +565,45 @@ static struct loader_callbacks cb = {
.getenv = cb_getenv, .getenv = cb_getenv,
}; };
static int
altcons_open(char *path)
{
struct stat sb;
int err;
int fd;
/*
* Allow stdio to be passed in so that the same string
* can be used for the bhyveload console and bhyve com-port
* parameters
*/
if (!strcmp(path, "stdio"))
return (0);
err = stat(path, &sb);
if (err == 0) {
if (!S_ISCHR(sb.st_mode))
err = ENOTSUP;
else {
fd = open(path, O_RDWR | O_NONBLOCK);
if (fd < 0)
err = errno;
else
consin_fd = consout_fd = fd;
}
}
return (err);
}
static void static void
usage(void) usage(void)
{ {
fprintf(stderr, fprintf(stderr,
"usage: %s [-m mem-size] [-d <disk-path>] [-h <host-path>]\n" "usage: %s [-m mem-size] [-d <disk-path>] [-h <host-path>]\n"
" %*s [-e <name=value>] <vmname>\n", progname, " %*s [-e <name=value>] [-c <console-device>] <vmname>\n",
progname,
(int)strlen(progname), ""); (int)strlen(progname), "");
exit(1); exit(1);
} }
@ -589,8 +622,16 @@ main(int argc, char** argv)
mem_size = 256 * MB; mem_size = 256 * MB;
disk_image = NULL; disk_image = NULL;
while ((opt = getopt(argc, argv, "d:e:h:m:")) != -1) { consin_fd = STDIN_FILENO;
consout_fd = STDOUT_FILENO;
while ((opt = getopt(argc, argv, "c:d:e:h:m:")) != -1) {
switch (opt) { switch (opt) {
case 'c':
error = altcons_open(optarg);
if (error != 0)
errx(EX_USAGE, "Could not open '%s'", optarg);
break;
case 'd': case 'd':
disk_image = optarg; disk_image = optarg;
break; break;
@ -640,11 +681,13 @@ main(int argc, char** argv)
exit(1); exit(1);
} }
tcgetattr(0, &term); tcgetattr(consout_fd, &term);
oldterm = term; oldterm = term;
term.c_lflag &= ~(ICANON|ECHO); cfmakeraw(&term);
term.c_iflag &= ~ICRNL; term.c_cflag |= CLOCAL;
tcsetattr(0, TCSAFLUSH, &term);
tcsetattr(consout_fd, TCSAFLUSH, &term);
h = dlopen("/boot/userboot.so", RTLD_LOCAL); h = dlopen("/boot/userboot.so", RTLD_LOCAL);
if (!h) { if (!h) {
printf("%s\n", dlerror()); printf("%s\n", dlerror());