- Consolidate all console related code to term.c - Use timer based approach instead of thread to deal with user input - Introduce --enable-virtio-console option, this option disables serial console output NOTE: Please add something like this: T2:23:respawn:/sbin/getty -L hvc0 9600 vt100 to your /etc/inittab file to get a virtio console login. Signed-off-by: Asias He <asias.hejun@xxxxxxxxx> --- tools/kvm/8250-serial.c | 40 +----- tools/kvm/Makefile | 2 + tools/kvm/console-virtio.c | 218 ++++++++++++++++++++++++++++++++ tools/kvm/include/kvm/8250-serial.h | 2 +- tools/kvm/include/kvm/console-virtio.h | 9 ++ tools/kvm/include/kvm/term.h | 17 +++ tools/kvm/main.c | 48 ++----- tools/kvm/term.c | 89 +++++++++++++ 8 files changed, 357 insertions(+), 68 deletions(-) create mode 100644 tools/kvm/console-virtio.c create mode 100644 tools/kvm/include/kvm/console-virtio.h create mode 100644 tools/kvm/include/kvm/term.h create mode 100644 tools/kvm/term.c diff --git a/tools/kvm/8250-serial.c b/tools/kvm/8250-serial.c index 939e9f5..e92ff89 100644 --- a/tools/kvm/8250-serial.c +++ b/tools/kvm/8250-serial.c @@ -3,12 +3,11 @@ #include "kvm/read-write.h" #include "kvm/ioport.h" #include "kvm/util.h" +#include "kvm/term.h" #include "kvm/kvm.h" #include <linux/serial_reg.h> -#include <stdbool.h> -#include <poll.h> struct serial8250_device { uint16_t iobase; @@ -54,26 +53,6 @@ static struct serial8250_device devices[] = { }, }; -static int read_char(int fd) -{ - char c; - - if (read_in_full(fd, &c, 1) == 0) - return -1; - - return c; -} - -static bool is_readable(int fd) -{ - struct pollfd pollfd = (struct pollfd) { - .fd = fd, - .events = POLLIN, - }; - - return poll(&pollfd, 1, 0) > 0; -} - static void serial8250__receive(struct kvm *self, struct serial8250_device *dev) { int c; @@ -81,10 +60,11 @@ static void serial8250__receive(struct kvm *self, struct serial8250_device *dev) if (dev->lsr & UART_LSR_DR) return; - if (!is_readable(STDIN_FILENO)) + if (!term_readable(CONSOLE_8250)) return; - c = read_char(STDIN_FILENO); + c = term_getc(CONSOLE_8250); + if (c < 0) return; @@ -95,7 +75,7 @@ static void serial8250__receive(struct kvm *self, struct serial8250_device *dev) /* * Interrupts are injected for ttyS0 only. */ -void serial8250__interrupt(struct kvm *self) +void serial8250__inject_interrupt(struct kvm *self) { struct serial8250_device *dev = &devices[0]; @@ -170,15 +150,9 @@ static bool serial8250_out(struct kvm *self, uint16_t port, void *data, int size } else { switch (offset) { case UART_TX: { - char *p = data; - int i; - + char *addr = data; if (!(dev->mcr & UART_MCR_LOOP)) { - while (count--) { - for (i = 0; i < size; i++) - fprintf(stdout, "%c", *p++); - } - fflush(stdout); + term_putc(CONSOLE_8250, addr, size * count); } dev->iir = UART_IIR_NO_INT; break; diff --git a/tools/kvm/Makefile b/tools/kvm/Makefile index 747aa51..55f342d 100644 --- a/tools/kvm/Makefile +++ b/tools/kvm/Makefile @@ -14,6 +14,7 @@ TAGS = ctags OBJS += 8250-serial.o OBJS += blk-virtio.o +OBJS += console-virtio.o OBJS += cpuid.o OBJS += read-write.o OBJS += disk-image.o @@ -24,6 +25,7 @@ OBJS += main.o OBJS += mmio.o OBJS += pci.o OBJS += util.o +OBJS += term.o DEPS := $(patsubst %.o,%.d,$(OBJS)) diff --git a/tools/kvm/console-virtio.c b/tools/kvm/console-virtio.c new file mode 100644 index 0000000..366d0ab --- /dev/null +++ b/tools/kvm/console-virtio.c @@ -0,0 +1,218 @@ +#include "kvm/console-virtio.h" +#include "kvm/virtio_pci.h" +#include "kvm/disk-image.h" +#include "kvm/virtqueue.h" +#include "kvm/ioport.h" +#include "kvm/util.h" +#include "kvm/term.h" +#include "kvm/kvm.h" +#include "kvm/pci.h" + +#include <linux/virtio_console.h> +#include <linux/virtio_ring.h> +#include <linux/virtio_blk.h> + +#include <sys/uio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <inttypes.h> +#include <termios.h> +#include <assert.h> +#include <unistd.h> +#include <fcntl.h> + +#define VIRTIO_CONSOLE_IRQ 14 +#define VIRTIO_CONSOLE_QUEUE_SIZE 128 +#define VIRTIO_CONSOLE_NUM_QUEUES 2 +#define VIRTIO_CONSOLE_RX_QUEUE 0 +#define VIRTIO_CONSOLE_TX_QUEUE 1 +#define PCI_VIRTIO_CONSOLE_DEVNUM 2 + +struct console_device { + struct virt_queue vqs[VIRTIO_CONSOLE_NUM_QUEUES]; + struct virtio_console_config console_config; + uint32_t host_features; + uint32_t guest_features; + uint16_t config_vector; + uint8_t status; + uint16_t queue_selector; +}; + +static struct console_device console_device = { + .console_config = { + .cols = 80, + .rows = 24, + .max_nr_ports = 1, + }, + + .host_features = 0, +}; + +/* + * Interrupts are injected for hvc0 only. + */ +void virtio_console__inject_interrupt(struct kvm *self) +{ + struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE]; + struct virt_queue *vq; + uint16_t out, in; + uint16_t head; + int len; + + vq = &console_device.vqs[VIRTIO_CONSOLE_RX_QUEUE]; + + if (term_readable(CONSOLE_VIRTIO) && virt_queue__available(vq)) { + head = virt_queue__get_iov(vq, iov, &out, &in, self); + len = term_getc_iov(CONSOLE_VIRTIO, iov, in); + virt_queue__set_used_elem(vq, head, len); + kvm__irq_line(self, VIRTIO_CONSOLE_IRQ, 1); + } +} + +static bool virtio_console_pci_io_device_specific_in(void *data, unsigned long offset, int size, uint32_t count) +{ + uint8_t *config_space = (uint8_t *) &console_device.console_config; + + if (size != 1 || count != 1) + return false; + + if ((offset - VIRTIO_PCI_CONFIG_NOMSI) > sizeof(struct virtio_console_config)) + error("config offset is too big: %li", offset - VIRTIO_PCI_CONFIG_NOMSI); + + ioport__write8(data, config_space[offset - VIRTIO_PCI_CONFIG_NOMSI]); + + return true; +} + +static bool virtio_console_pci_io_in(struct kvm *self, uint16_t port, void *data, int size, uint32_t count) +{ + unsigned long offset = port - IOPORT_VIRTIO_CONSOLE; + + switch (offset) { + case VIRTIO_PCI_HOST_FEATURES: + ioport__write32(data, console_device.host_features); + break; + case VIRTIO_PCI_GUEST_FEATURES: + return false; + case VIRTIO_PCI_QUEUE_PFN: + ioport__write32(data, console_device.vqs[console_device.queue_selector].pfn); + break; + case VIRTIO_PCI_QUEUE_NUM: + ioport__write16(data, VIRTIO_CONSOLE_QUEUE_SIZE); + break; + case VIRTIO_PCI_QUEUE_SEL: + case VIRTIO_PCI_QUEUE_NOTIFY: + return false; + case VIRTIO_PCI_STATUS: + ioport__write8(data, console_device.status); + break; + case VIRTIO_PCI_ISR: + ioport__write8(data, 0x1); + kvm__irq_line(self, VIRTIO_CONSOLE_IRQ, 0); + break; + case VIRTIO_MSI_CONFIG_VECTOR: + ioport__write16(data, console_device.config_vector); + break; + default: + return virtio_console_pci_io_device_specific_in(data, offset, size, count); + }; + + return true; +} + +static void virtio_console_handle_callback(struct kvm *self, uint16_t queue_index) +{ + struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE]; + struct virt_queue *vq; + uint16_t out, in; + uint16_t head; + uint32_t len; + + vq = &console_device.vqs[queue_index]; + + if (queue_index == VIRTIO_CONSOLE_TX_QUEUE) { + + while (virt_queue__available(vq)) { + head = virt_queue__get_iov(vq, iov, &out, &in, self); + len = term_putc_iov(CONSOLE_VIRTIO, iov, out); + virt_queue__set_used_elem(vq, head, len); + } + + kvm__irq_line(self, VIRTIO_CONSOLE_IRQ, 1); + } +} + +static bool virtio_console_pci_io_out(struct kvm *self, uint16_t port, void *data, int size, uint32_t count) +{ + unsigned long offset = port - IOPORT_VIRTIO_CONSOLE; + + switch (offset) { + case VIRTIO_PCI_GUEST_FEATURES: + console_device.guest_features = ioport__read32(data); + break; + case VIRTIO_PCI_QUEUE_PFN: { + struct virt_queue *queue; + void *p; + + assert(console_device.queue_selector < VIRTIO_CONSOLE_NUM_QUEUES); + + queue = &console_device.vqs[console_device.queue_selector]; + queue->pfn = ioport__read32(data); + p = guest_flat_to_host(self, queue->pfn << 12); + + vring_init(&queue->vring, VIRTIO_CONSOLE_QUEUE_SIZE, p, 4096); + + break; + } + case VIRTIO_PCI_QUEUE_SEL: + console_device.queue_selector = ioport__read16(data); + break; + case VIRTIO_PCI_QUEUE_NOTIFY: { + uint16_t queue_index; + queue_index = ioport__read16(data); + virtio_console_handle_callback(self, queue_index); + break; + } + case VIRTIO_PCI_STATUS: + console_device.status = ioport__read8(data); + break; + case VIRTIO_MSI_CONFIG_VECTOR: + console_device.config_vector = VIRTIO_MSI_NO_VECTOR; + break; + case VIRTIO_MSI_QUEUE_VECTOR: + break; + default: + return false; + }; + + return true; +} + +static struct ioport_operations virtio_console_io_ops = { + .io_in = virtio_console_pci_io_in, + .io_out = virtio_console_pci_io_out, +}; + +#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1002 +#define PCI_SUBSYSTEM_VENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBSYSTEM_ID_VIRTIO_CONSOLE 0x0003 + +static struct pci_device_header virtio_console_pci_device = { + .vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET, + .device_id = PCI_DEVICE_ID_VIRTIO_CONSOLE, + .header_type = PCI_HEADER_TYPE_NORMAL, + .revision_id = 0, + .class = (0x07 << 8) | (0x80 << 4) | (0x0 << 0), + .subsys_vendor_id = PCI_SUBSYSTEM_VENDOR_ID_REDHAT_QUMRANET, + .subsys_id = PCI_SUBSYSTEM_ID_VIRTIO_CONSOLE, + .bar[0] = IOPORT_VIRTIO_CONSOLE | PCI_BASE_ADDRESS_SPACE_IO, + .irq_pin = 3, + .irq_line = VIRTIO_CONSOLE_IRQ, +}; + +void virtio_console__init(struct kvm *self) +{ + pci__register(&virtio_console_pci_device, PCI_VIRTIO_CONSOLE_DEVNUM); + ioport__register(IOPORT_VIRTIO_CONSOLE, &virtio_console_io_ops, IOPORT_VIRTIO_CONSOLE_SIZE); +} diff --git a/tools/kvm/include/kvm/8250-serial.h b/tools/kvm/include/kvm/8250-serial.h index 05ee096..73ed078 100644 --- a/tools/kvm/include/kvm/8250-serial.h +++ b/tools/kvm/include/kvm/8250-serial.h @@ -4,6 +4,6 @@ struct kvm; void serial8250__init(struct kvm *kvm); -void serial8250__interrupt(struct kvm *kvm); +void serial8250__inject_interrupt(struct kvm *kvm); #endif /* KVM__8250_SERIAL_H */ diff --git a/tools/kvm/include/kvm/console-virtio.h b/tools/kvm/include/kvm/console-virtio.h new file mode 100644 index 0000000..d2e5d19 --- /dev/null +++ b/tools/kvm/include/kvm/console-virtio.h @@ -0,0 +1,9 @@ +#ifndef KVM__CONSOLE_VIRTIO_H +#define KVM__CONSOLE_VIRTIO_H + +struct kvm; + +void virtio_console__init(struct kvm *self); +void virtio_console__inject_interrupt(struct kvm *self); + +#endif /* KVM__CONSOLE_VIRTIO_H */ diff --git a/tools/kvm/include/kvm/term.h b/tools/kvm/include/kvm/term.h new file mode 100644 index 0000000..4d580e1 --- /dev/null +++ b/tools/kvm/include/kvm/term.h @@ -0,0 +1,17 @@ +#ifndef KVM__TERM_H +#define KVM__TERM_H + +#include <sys/uio.h> + +#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); + +bool term_readable(int who); +void term_init(void); + +#endif /* KVM__TERM_H */ diff --git a/tools/kvm/main.c b/tools/kvm/main.c index 7beb6a6..5bcefde 100644 --- a/tools/kvm/main.c +++ b/tools/kvm/main.c @@ -2,12 +2,13 @@ #include "kvm/8250-serial.h" #include "kvm/blk-virtio.h" +#include "kvm/console-virtio.h" #include "kvm/disk-image.h" #include "kvm/util.h" #include "kvm/pci.h" +#include "kvm/term.h" #include <inttypes.h> -#include <termios.h> #include <signal.h> #include <stdint.h> #include <stdlib.h> @@ -16,11 +17,12 @@ #include <stdio.h> extern bool ioport_debug; +extern int active_console; static void usage(char *argv[]) { fprintf(stderr, " usage: %s " - "[--single-step] [--ioport-debug] " + "[--single-step] [--ioport-debug] [--enable-virtio-console] " "[--kvm-dev=<device>] [--mem=<size-in-MiB>] [--params=<kernel-params>] " "[--initrd=<initrd>] [--kernel=]<kernel-image> [--image=]<disk-image>\n", argv[0]); @@ -29,32 +31,6 @@ static void usage(char *argv[]) static struct kvm *kvm; -static struct termios orig_term; - -static void setup_console(void) -{ - struct termios term; - - if (tcgetattr(STDIN_FILENO, &orig_term) < 0) - die("unable to save initial standard input settings"); - - term = orig_term; - - term.c_lflag &= ~(ICANON|ECHO); - - tcsetattr(STDIN_FILENO, TCSANOW, &term); -} - -static void cleanup_console(void) -{ - tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); -} - -static void shutdown(void) -{ - cleanup_console(); -} - static void handle_sigint(int sig) { exit(1); @@ -65,7 +41,6 @@ static void handle_sigquit(int sig) kvm__show_registers(kvm); kvm__show_code(kvm); kvm__show_page_tables(kvm); - kvm__delete(kvm); exit(1); @@ -92,10 +67,6 @@ int main(int argc, char *argv[]) signal(SIGQUIT, handle_sigquit); signal(SIGINT, handle_sigint); - setup_console(); - - atexit(shutdown); - for (i = 1; i < argc; i++) { if (option_matches(argv[i], "--kernel=")) { kernel_filename = &argv[i][9]; @@ -115,6 +86,9 @@ int main(int argc, char *argv[]) } else if (option_matches(argv[i], "--single-step")) { single_step = true; continue; + } else if (option_matches(argv[i], "--enable-virtio-console")) { + active_console = CONSOLE_VIRTIO; + continue; } else if (option_matches(argv[i], "--mem=")) { unsigned long val = atol(&argv[i][6]) << 20; if (val < ram_size) @@ -138,6 +112,8 @@ int main(int argc, char *argv[]) if (!kernel_filename) usage(argv); + term_init(); + kvm = kvm__init(kvm_dev, ram_size); if (image_filename) { @@ -168,10 +144,13 @@ int main(int argc, char *argv[]) kvm__enable_singlestep(kvm); serial8250__init(kvm); + pci__init(); blk_virtio__init(kvm); + virtio_console__init(kvm); + kvm__start_timer(kvm); for (;;) { @@ -210,7 +189,8 @@ int main(int argc, char *argv[]) break; } case KVM_EXIT_INTR: { - serial8250__interrupt(kvm); + serial8250__inject_interrupt(kvm); + virtio_console__inject_interrupt(kvm); break; } case KVM_EXIT_SHUTDOWN: diff --git a/tools/kvm/term.c b/tools/kvm/term.c new file mode 100644 index 0000000..0ac53c0 --- /dev/null +++ b/tools/kvm/term.c @@ -0,0 +1,89 @@ +#include <poll.h> +#include <stdbool.h> +#include <termios.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/uio.h> + +#include "kvm/read-write.h" +#include "kvm/term.h" +#include "kvm/util.h" + +static struct termios orig_term; + +int active_console = CONSOLE_8250; + +int term_getc(int who) +{ + int c; + + if (who != active_console) + return -1; + + if (read_in_full(STDIN_FILENO, &c, 1) < 0) + return -1; + return c; +} + +int term_putc(int who, char *addr, int cnt) +{ + if (who != active_console) + return -1; + + while (cnt--) { + fprintf(stdout, "%c", *addr++); + } + + fflush(stdout); + return cnt; +} + +int term_getc_iov(int who, struct iovec *iov, int iovcnt) +{ + if (who != active_console) + return -1; + + return readv(STDIN_FILENO, iov, iovcnt); +} + +int term_putc_iov(int who, struct iovec *iov, int iovcnt) +{ + if (who != active_console) + return -1; + + return writev(STDOUT_FILENO, iov, iovcnt); +} + +bool term_readable(int who) +{ + struct pollfd pollfd = (struct pollfd) { + .fd = STDIN_FILENO, + .events = POLLIN, + .revents = 0, + }; + + if (who != active_console) + return false; + + return poll(&pollfd, 1, 0) > 0; +} + +static void term_cleanup(void) +{ + tcsetattr(STDIN_FILENO, TCSANOW, &orig_term); +} + +void term_init(void) +{ + struct termios term; + + if (tcgetattr(STDIN_FILENO, &orig_term) < 0) + die("unable to save initial standard input settings"); + + term = orig_term; + term.c_lflag &= ~(ICANON |ECHO | ISIG); + tcsetattr(STDIN_FILENO, TCSANOW, &term); + + atexit(term_cleanup); +} + -- 1.7.4.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