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:
parent
0d8dc7cc39
commit
6380102c7f
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user