This patch adds the '-tty' option to 'kvm run' which allows the user to remap a guest TTY into a PTS on the host. Usage: 'kvm run --tty [id] [other options]' The tty will be mapped to a pts and will be printed on the screen: ' Info: Assigned terminal 1 to pty /dev/pts/X' At this point, it is possible to communicate with the guest using that pty. This is useful for debugging guest kernel using KGDB: 1. Run the guest: 'kvm run -k [vmlinuz] -p "kdbgoc=ttyS1 kdbgwait" --tty 1' And see which PTY got assigned to ttyS1. 2. Run GDB on the host: 'gdb [vmlinuz]' 3. Connect to the guest (from within GDB): 'target remote /dev/pty/X' 4. Start debugging! (enter 'continue' to continue boot). Cc: David Evensky <evensky@xxxxxxxxxxxxxxxxxxxx> Signed-off-by: Sasha Levin <levinsasha928@xxxxxxxxx> --- tools/kvm/Makefile | 1 + tools/kvm/builtin-run.c | 12 ++++++++ tools/kvm/hw/serial.c | 46 ++++++++++++++++++-------------- tools/kvm/include/kvm/term.h | 11 ++++--- tools/kvm/term.c | 60 +++++++++++++++++++++++++++++++++-------- tools/kvm/virtio/console.c | 6 ++-- 6 files changed, 96 insertions(+), 40 deletions(-) diff --git a/tools/kvm/Makefile b/tools/kvm/Makefile index efa032d..fef624d 100644 --- a/tools/kvm/Makefile +++ b/tools/kvm/Makefile @@ -115,6 +115,7 @@ OBJS += bios/bios-rom.o LIBS += -lrt LIBS += -lpthread +LIBS += -lutil # Additional ARCH settings for x86 ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \ diff --git a/tools/kvm/builtin-run.c b/tools/kvm/builtin-run.c index 5dafb15..b5c63ca 100644 --- a/tools/kvm/builtin-run.c +++ b/tools/kvm/builtin-run.c @@ -172,6 +172,15 @@ static int virtio_9p_rootdir_parser(const struct option *opt, const char *arg, i return 0; } +static int tty_parser(const struct option *opt, const char *arg, int unset) +{ + int tty = atoi(arg); + + term_set_tty(tty); + + return 0; +} + static int shmem_parser(const struct option *opt, const char *arg, int unset) { const u64 default_size = SHMEM_DEFAULT_SIZE; @@ -316,6 +325,9 @@ static const struct option options[] = { OPT_STRING('\0', "console", &console, "serial or virtio", "Console to use"), OPT_STRING('\0', "dev", &dev, "device_file", "KVM device file"), + OPT_CALLBACK('\0', "tty", NULL, "tty id", + "Remap guest TTY into a pty on the host", + tty_parser), OPT_GROUP("Kernel options:"), OPT_STRING('k', "kernel", &kernel_filename, "kernel", diff --git a/tools/kvm/hw/serial.c b/tools/kvm/hw/serial.c index b3b233f..11fa5d4 100644 --- a/tools/kvm/hw/serial.c +++ b/tools/kvm/hw/serial.c @@ -14,6 +14,7 @@ struct serial8250_device { pthread_mutex_t mutex; + u8 id; u16 iobase; u8 irq; @@ -42,6 +43,7 @@ static struct serial8250_device devices[] = { [0] = { .mutex = PTHREAD_MUTEX_INITIALIZER, + .id = 0, .iobase = 0x3f8, .irq = 4, @@ -51,6 +53,7 @@ static struct serial8250_device devices[] = { [1] = { .mutex = PTHREAD_MUTEX_INITIALIZER, + .id = 1, .iobase = 0x2f8, .irq = 3, @@ -60,6 +63,7 @@ static struct serial8250_device devices[] = { [2] = { .mutex = PTHREAD_MUTEX_INITIALIZER, + .id = 2, .iobase = 0x3e8, .irq = 4, @@ -69,6 +73,7 @@ static struct serial8250_device devices[] = { [3] = { .mutex = PTHREAD_MUTEX_INITIALIZER, + .id = 3, .iobase = 0x2e8, .irq = 3, @@ -111,10 +116,10 @@ static void serial8250__receive(struct kvm *kvm, struct serial8250_device *dev) return; } - if (!term_readable(CONSOLE_8250)) + if (!term_readable(CONSOLE_8250, dev->id)) return; - c = term_getc(CONSOLE_8250); + c = term_getc(CONSOLE_8250, dev->id); if (c < 0) return; @@ -123,30 +128,31 @@ static void serial8250__receive(struct kvm *kvm, struct serial8250_device *dev) dev->lsr |= UART_LSR_DR; } -/* - * Interrupts are injected for ttyS0 only. - */ void serial8250__inject_interrupt(struct kvm *kvm) { - struct serial8250_device *dev = &devices[0]; + int i; - mutex_lock(&dev->mutex); + for (i = 0; i < 4; i++) { + struct serial8250_device *dev = &devices[i]; - serial8250__receive(kvm, dev); + mutex_lock(&dev->mutex); - if (dev->ier & UART_IER_RDI && dev->lsr & UART_LSR_DR) - dev->iir = UART_IIR_RDI; - else if (dev->ier & UART_IER_THRI) - dev->iir = UART_IIR_THRI; - else - dev->iir = UART_IIR_NO_INT; + serial8250__receive(kvm, dev); - if (dev->iir != UART_IIR_NO_INT) { - kvm__irq_line(kvm, dev->irq, 0); - kvm__irq_line(kvm, dev->irq, 1); - } + if (dev->ier & UART_IER_RDI && dev->lsr & UART_LSR_DR) + dev->iir = UART_IIR_RDI; + else if (dev->ier & UART_IER_THRI) + dev->iir = UART_IIR_THRI; + else + dev->iir = UART_IIR_NO_INT; - mutex_unlock(&dev->mutex); + if (dev->iir != UART_IIR_NO_INT) { + kvm__irq_line(kvm, dev->irq, 0); + kvm__irq_line(kvm, dev->irq, 1); + } + + mutex_unlock(&dev->mutex); + } } void serial8250__inject_sysrq(struct kvm *kvm) @@ -217,7 +223,7 @@ static bool serial8250_out(struct ioport *ioport, struct kvm *kvm, u16 port, voi char *addr = data; if (!(dev->mcr & UART_MCR_LOOP)) - term_putc(CONSOLE_8250, addr, size); + term_putc(CONSOLE_8250, addr, size, dev->id); dev->iir = UART_IIR_NO_INT; break; diff --git a/tools/kvm/include/kvm/term.h b/tools/kvm/include/kvm/term.h index 4d580e1..37ec731 100644 --- a/tools/kvm/include/kvm/term.h +++ b/tools/kvm/include/kvm/term.h @@ -6,12 +6,13 @@ #define CONSOLE_8250 1 #define CONSOLE_VIRTIO 2 -int term_putc_iov(int who, struct iovec *iov, int iovcnt); -int term_getc_iov(int who, struct iovec *iov, int iovcnt); -int term_putc(int who, char *addr, int cnt); -int term_getc(int who); +int term_putc_iov(int who, struct iovec *iov, int iovcnt, int term); +int term_getc_iov(int who, struct iovec *iov, int iovcnt, int term); +int term_putc(int who, char *addr, int cnt, int term); +int term_getc(int who, int term); -bool term_readable(int who); +bool term_readable(int who, int term); +void term_set_tty(int term); void term_init(void); #endif /* KVM__TERM_H */ diff --git a/tools/kvm/term.c b/tools/kvm/term.c index fa4382d..45e8bab 100644 --- a/tools/kvm/term.c +++ b/tools/kvm/term.c @@ -5,6 +5,8 @@ #include <unistd.h> #include <sys/uio.h> #include <signal.h> +#include <pty.h> +#include <utmp.h> #include "kvm/read-write.h" #include "kvm/term.h" @@ -20,14 +22,16 @@ bool term_got_escape = false; int active_console; -int term_getc(int who) +int term_fds[4][2]; + +int term_getc(int who, int term) { int c; if (who != active_console) return -1; - if (read_in_full(STDIN_FILENO, &c, 1) < 0) + if (read_in_full(term_fds[term][0], &c, 1) < 0) return -1; c &= 0xff; @@ -48,26 +52,27 @@ int term_getc(int who) return c; } -int term_putc(int who, char *addr, int cnt) +int term_putc(int who, char *addr, int cnt, int term) { + int ret; + if (who != active_console) return -1; while (cnt--) - fprintf(stdout, "%c", *addr++); + ret = write(term_fds[term][1], addr++, 1); - fflush(stdout); return cnt; } -int term_getc_iov(int who, struct iovec *iov, int iovcnt) +int term_getc_iov(int who, struct iovec *iov, int iovcnt, int term) { int c; if (who != active_console) return 0; - c = term_getc(who); + c = term_getc(who, term); if (c < 0) return 0; @@ -77,18 +82,18 @@ int term_getc_iov(int who, struct iovec *iov, int iovcnt) return sizeof(char); } -int term_putc_iov(int who, struct iovec *iov, int iovcnt) +int term_putc_iov(int who, struct iovec *iov, int iovcnt, int term) { if (who != active_console) return 0; - return writev(STDOUT_FILENO, iov, iovcnt); + return writev(term_fds[term][1], iov, iovcnt); } -bool term_readable(int who) +bool term_readable(int who, int term) { struct pollfd pollfd = (struct pollfd) { - .fd = STDIN_FILENO, + .fd = term_fds[term][0], .events = POLLIN, .revents = 0, }; @@ -101,7 +106,10 @@ bool term_readable(int who) static void term_cleanup(void) { - tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); + int i; + + for (i = 0; i < 4; i++) + tcsetattr(term_fds[i][0], TCSANOW, &orig_term); } static void term_sig_cleanup(int sig) @@ -111,9 +119,31 @@ static void term_sig_cleanup(int sig) raise(sig); } +void term_set_tty(int term) +{ + struct termios orig_term; + int master, slave; + char new_pty[PATH_MAX]; + + if (tcgetattr(STDIN_FILENO, &orig_term) < 0) + die("unable to save initial standard input settings"); + + orig_term.c_lflag &= ~(ICANON | ECHO | ISIG); + + if (openpty(&master, &slave, new_pty, &orig_term, NULL) < 0) + return; + + close(slave); + + pr_info("Assigned terminal %d to pty %s\n", term, new_pty); + + term_fds[term][0] = term_fds[term][1] = master; +} + void term_init(void) { struct termios term; + int i; if (tcgetattr(STDIN_FILENO, &orig_term) < 0) die("unable to save initial standard input settings"); @@ -122,6 +152,12 @@ void term_init(void) term.c_lflag &= ~(ICANON | ECHO | ISIG); tcsetattr(STDIN_FILENO, TCSANOW, &term); + for (i = 0; i < 4; i++) + if (term_fds[i][0] == 0) { + term_fds[i][0] = STDIN_FILENO; + term_fds[i][1] = STDOUT_FILENO; + } + signal(SIGTERM, term_sig_cleanup); atexit(term_cleanup); } diff --git a/tools/kvm/virtio/console.c b/tools/kvm/virtio/console.c index c0ccd6c..b880162 100644 --- a/tools/kvm/virtio/console.c +++ b/tools/kvm/virtio/console.c @@ -67,9 +67,9 @@ static void virtio_console__inject_interrupt_callback(struct kvm *kvm, void *par vq = param; - if (term_readable(CONSOLE_VIRTIO) && virt_queue__available(vq)) { + if (term_readable(CONSOLE_VIRTIO, 0) && virt_queue__available(vq)) { head = virt_queue__get_iov(vq, iov, &out, &in, kvm); - len = term_getc_iov(CONSOLE_VIRTIO, iov, in); + len = term_getc_iov(CONSOLE_VIRTIO, iov, in, 0); virt_queue__set_used_elem(vq, head, len); virtio_pci__signal_vq(kvm, &cdev.vpci, vq - cdev.vqs); } @@ -100,7 +100,7 @@ static void virtio_console_handle_callback(struct kvm *kvm, void *param) while (virt_queue__available(vq)) { head = virt_queue__get_iov(vq, iov, &out, &in, kvm); - len = term_putc_iov(CONSOLE_VIRTIO, iov, out); + len = term_putc_iov(CONSOLE_VIRTIO, iov, out, 0); virt_queue__set_used_elem(vq, head, len); } -- 1.7.6.1 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html