/* * Virtio Console Device * * Copyright IBM, Corp. 2008 * * Authors: * Christian Ehrhardt * * This work is licensed under the terms of the GNU GPL, version 2. See * the COPYING file in the top-level directory. * */ #include "hw.h" #include "qemu-char.h" #include "virtio.h" #include "virtio-console.h" typedef struct VirtIOConsole { VirtIODevice vdev; VirtQueue *ivq, *dvq; CharDriverState *chr; } VirtIOConsole; static VirtIOConsole *to_virtio_console(VirtIODevice *vdev) { return (VirtIOConsole *)vdev; } static void virtio_console_handle_output(VirtIODevice *vdev, VirtQueue *vq) { VirtIOConsole *s = to_virtio_console(vdev); VirtQueueElement elem; while (virtqueue_pop(vq, &elem)) { ssize_t len = 0; int d; for (d = 0; d < elem.out_num; d++) { len += qemu_chr_write(s->chr, (uint8_t *)elem.out_sg[d].iov_base, elem.out_sg[d].iov_len); } virtqueue_push(vq, &elem, len); virtio_notify(vdev, vq); } } static void virtio_console_handle_input(VirtIODevice *vdev, VirtQueue *vq) { } static uint32_t virtio_console_get_features(VirtIODevice *vdev) { return 0; } static int vcon_can_read(void *opaque) { VirtIOConsole *s = (VirtIOConsole *) opaque; if (!virtio_queue_ready(s->ivq) || !(s->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) || virtio_queue_empty(s->ivq)) return 0; /* current implementations have a page sized buffer. * We fall back to a one byte per read if there is not enough room. * It would be cool to have a function that returns the available byte * instead of checking for a limit */ if (virtqueue_avail_bytes(s->ivq, TARGET_PAGE_SIZE, 0)) return TARGET_PAGE_SIZE; if (virtqueue_avail_bytes(s->ivq, 1, 0)) return 1; return 0; } static void vcon_read(void *opaque, const uint8_t *buf, int size) { VirtIOConsole *s = (VirtIOConsole *) opaque; VirtQueueElement elem; int offset = 0; /* The current kernel implementation has only one outstanding input * buffer of PAGE_SIZE. Nevertheless, this function is prepared to * handle multiple buffers with multiple sg element for input */ while (offset < size) { int i = 0; if (!virtqueue_pop(s->ivq, &elem)) break; while (offset < size && i < elem.in_num) { int len = MIN(elem.in_sg[i].iov_len, size - offset); memcpy(elem.in_sg[i].iov_base, buf + offset, len); offset += len; i++; } virtqueue_push(s->ivq, &elem, size); } virtio_notify(&s->vdev, s->ivq); } static void vcon_event(void *opaque, int event) { /* we will ignore any event for the time being */ } static void virtio_console_save(QEMUFile *f, void *opaque) { VirtIOConsole *s = opaque; virtio_save(&s->vdev, f); } static int virtio_console_load(QEMUFile *f, void *opaque, int version_id) { VirtIOConsole *s = opaque; if (version_id != 1) return -EINVAL; virtio_load(&s->vdev, f); return 0; } static void virtio_console_init(PCIDevice *pci_dev) { VirtIOConsole *s; s = (VirtIOConsole *)virtio_init_pci(pci_dev, "virtio-console", PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_DEVICE_ID_VIRTIO_CONSOLE, PCI_VENDOR_ID_REDHAT_QUMRANET, VIRTIO_ID_CONSOLE, PCI_CLASS_DISPLAY_OTHER, 0x00, 0); s->vdev.get_features = virtio_console_get_features; s->ivq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_input); s->dvq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_output); s->chr = qdev_init_chardev(&pci_dev->qdev); qemu_chr_add_handlers(s->chr, vcon_can_read, vcon_read, vcon_event, s); register_savevm("virtio-console", -1, 1, virtio_console_save, virtio_console_load, s); } static void virtio_console_register_devices(void) { pci_qdev_register("virtio-console", sizeof(VirtIOConsole), virtio_console_init); } device_init(virtio_console_register_devices)