#include "kvm/virtio-console.h" #include "kvm/virtio-pci-dev.h" #include "kvm/disk-image.h" #include "kvm/virtio.h" #include "kvm/ioport.h" #include "kvm/util.h" #include "kvm/term.h" #include "kvm/mutex.h" #include "kvm/kvm.h" #include "kvm/pci.h" #include "kvm/threadpool.h" #include "kvm/irq.h" #include "kvm/guest_compat.h" #include #include #include #include #include #include #include #include #include #define VIRTIO_CONSOLE_QUEUE_SIZE 128 #define VIRTIO_CONSOLE_NUM_QUEUES 2 #define VIRTIO_CONSOLE_RX_QUEUE 0 #define VIRTIO_CONSOLE_TX_QUEUE 1 struct con_dev { struct mutex mutex; struct virtio_device vdev; struct virt_queue vqs[VIRTIO_CONSOLE_NUM_QUEUES]; struct virtio_console_config config; u32 features; pthread_cond_t poll_cond; int vq_ready; struct thread_pool__job jobs[VIRTIO_CONSOLE_NUM_QUEUES]; }; static struct con_dev cdev = { .mutex = MUTEX_INITIALIZER, .vq_ready = 0, .config = { .cols = 80, .rows = 24, .max_nr_ports = 1, }, }; static int compat_id = -1; /* * Interrupts are injected for hvc0 only. */ static void virtio_console__inject_interrupt_callback(struct kvm *kvm, void *param) { struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE]; struct virt_queue *vq; u16 out, in; u16 head; int len; if (kvm->cfg.active_console != CONSOLE_VIRTIO) return; mutex_lock(&cdev.mutex); vq = param; if (!cdev.vq_ready) pthread_cond_wait(&cdev.poll_cond, &cdev.mutex.mutex); if (term_readable(0) && virt_queue__available(vq)) { head = virt_queue__get_iov(vq, iov, &out, &in, kvm); len = term_getc_iov(kvm, iov, in, 0); virt_queue__set_used_elem(vq, head, len); cdev.vdev.ops->signal_vq(kvm, &cdev.vdev, vq - cdev.vqs); } mutex_unlock(&cdev.mutex); } void virtio_console__inject_interrupt(struct kvm *kvm) { virtio_console__inject_interrupt_callback(kvm, &cdev.vqs[VIRTIO_CONSOLE_RX_QUEUE]); } static void virtio_console_handle_callback(struct kvm *kvm, void *param) { struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE]; struct virt_queue *vq; u16 out, in; u16 head; u32 len; vq = param; /* * The current Linux implementation polls for the buffer * to be used, rather than waiting for an interrupt. * So there is no need to inject an interrupt for the tx path. */ while (virt_queue__available(vq)) { head = virt_queue__get_iov(vq, iov, &out, &in, kvm); len = term_putc_iov(iov, out, 0); virt_queue__set_used_elem(vq, head, len); } } static u8 *get_config(struct kvm *kvm, void *dev) { struct con_dev *cdev = dev; return ((u8 *)(&cdev->config)); } static u32 get_host_features(struct kvm *kvm, void *dev) { return 0; } static void set_guest_features(struct kvm *kvm, void *dev, u32 features) { /* Unused */ } static int init_vq(struct kvm *kvm, void *dev, u32 vq, u32 page_size, u32 align, u32 pfn) { struct virt_queue *queue; void *p; BUG_ON(vq >= VIRTIO_CONSOLE_NUM_QUEUES); compat__remove_message(compat_id); queue = &cdev.vqs[vq]; queue->pfn = pfn; p = virtio_get_vq(kvm, queue->pfn, page_size); vring_init(&queue->vring, VIRTIO_CONSOLE_QUEUE_SIZE, p, align); if (vq == VIRTIO_CONSOLE_TX_QUEUE) { thread_pool__init_job(&cdev.jobs[vq], kvm, virtio_console_handle_callback, queue); } else if (vq == VIRTIO_CONSOLE_RX_QUEUE) { thread_pool__init_job(&cdev.jobs[vq], kvm, virtio_console__inject_interrupt_callback, queue); /* Tell the waiting poll thread that we're ready to go */ mutex_lock(&cdev.mutex); cdev.vq_ready = 1; pthread_cond_signal(&cdev.poll_cond); mutex_unlock(&cdev.mutex); } return 0; } static int notify_vq(struct kvm *kvm, void *dev, u32 vq) { struct con_dev *cdev = dev; thread_pool__do_job(&cdev->jobs[vq]); return 0; } static int get_pfn_vq(struct kvm *kvm, void *dev, u32 vq) { struct con_dev *cdev = dev; return cdev->vqs[vq].pfn; } static int get_size_vq(struct kvm *kvm, void *dev, u32 vq) { return VIRTIO_CONSOLE_QUEUE_SIZE; } static int set_size_vq(struct kvm *kvm, void *dev, u32 vq, int size) { /* FIXME: dynamic */ return size; } static struct virtio_ops con_dev_virtio_ops = (struct virtio_ops) { .get_config = get_config, .get_host_features = get_host_features, .set_guest_features = set_guest_features, .init_vq = init_vq, .notify_vq = notify_vq, .get_pfn_vq = get_pfn_vq, .get_size_vq = get_size_vq, .set_size_vq = set_size_vq, }; int virtio_console__init(struct kvm *kvm) { if (kvm->cfg.active_console != CONSOLE_VIRTIO) return 0; pthread_cond_init(&cdev.poll_cond, NULL); virtio_init(kvm, &cdev, &cdev.vdev, &con_dev_virtio_ops, VIRTIO_DEFAULT_TRANS, PCI_DEVICE_ID_VIRTIO_CONSOLE, VIRTIO_ID_CONSOLE, PCI_CLASS_CONSOLE); if (compat_id == -1) compat_id = virtio_compat_add_message("virtio-console", "CONFIG_VIRTIO_CONSOLE"); return 0; } virtio_dev_init(virtio_console__init); int virtio_console__exit(struct kvm *kvm) { return 0; } virtio_dev_exit(virtio_console__exit);