version 1.1.1.2, 2018/04/24 17:24:53
|
version 1.1.1.5, 2018/04/24 18:39:37
|
Line 1
|
Line 1
|
/* |
/* |
* Virtio Console Device |
* Virtio Console and Generic Serial Port Devices |
* |
* |
* Copyright IBM, Corp. 2008 |
* Copyright Red Hat, Inc. 2009, 2010 |
* |
* |
* Authors: |
* Authors: |
* Christian Ehrhardt <ehrhardt@linux.vnet.ibm.com> |
* Amit Shah <amit.shah@redhat.com> |
* |
* |
* This work is licensed under the terms of the GNU GPL, version 2. See |
* This work is licensed under the terms of the GNU GPL, version 2. See |
* the COPYING file in the top-level directory. |
* the COPYING file in the top-level directory. |
* |
|
*/ |
*/ |
|
|
#include "hw.h" |
|
#include "qemu-char.h" |
#include "qemu-char.h" |
#include "virtio.h" |
#include "virtio-serial.h" |
#include "virtio-console.h" |
|
|
|
|
|
typedef struct VirtIOConsole |
typedef struct VirtConsole { |
{ |
VirtIOSerialPort port; |
VirtIODevice vdev; |
|
VirtQueue *ivq, *dvq; |
|
CharDriverState *chr; |
CharDriverState *chr; |
} VirtIOConsole; |
} VirtConsole; |
|
|
static VirtIOConsole *to_virtio_console(VirtIODevice *vdev) |
|
{ |
|
return (VirtIOConsole *)vdev; |
|
} |
|
|
|
static void virtio_console_handle_output(VirtIODevice *vdev, VirtQueue *vq) |
/* Callback function that's called when the guest sends us data */ |
|
static ssize_t flush_buf(VirtIOSerialPort *port, const uint8_t *buf, size_t len) |
{ |
{ |
VirtIOConsole *s = to_virtio_console(vdev); |
VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); |
VirtQueueElement elem; |
|
|
|
while (virtqueue_pop(vq, &elem)) { |
|
ssize_t len = 0; |
|
int d; |
|
|
|
for (d = 0; d < elem.out_num; d++) { |
return qemu_chr_write(vcon->chr, buf, len); |
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) |
/* Readiness of the guest to accept data on a port */ |
|
static int chr_can_read(void *opaque) |
{ |
{ |
} |
VirtConsole *vcon = opaque; |
|
|
static uint32_t virtio_console_get_features(VirtIODevice *vdev) |
return virtio_serial_guest_ready(&vcon->port); |
{ |
|
return 0; |
|
} |
} |
|
|
static int vcon_can_read(void *opaque) |
/* Send data from a char device over to the guest */ |
|
static void chr_read(void *opaque, const uint8_t *buf, int size) |
{ |
{ |
VirtIOConsole *s = (VirtIOConsole *) opaque; |
VirtConsole *vcon = opaque; |
|
|
if (!virtio_queue_ready(s->ivq) || |
virtio_serial_write(&vcon->port, buf, size); |
!(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) |
static void chr_event(void *opaque, int event) |
{ |
{ |
VirtIOConsole *s = (VirtIOConsole *) opaque; |
VirtConsole *vcon = opaque; |
VirtQueueElement elem; |
|
int offset = 0; |
switch (event) { |
|
case CHR_EVENT_OPENED: |
/* The current kernel implementation has only one outstanding input |
virtio_serial_open(&vcon->port); |
* buffer of PAGE_SIZE. Nevertheless, this function is prepared to |
break; |
* handle multiple buffers with multiple sg element for input */ |
case CHR_EVENT_CLOSED: |
while (offset < size) { |
virtio_serial_close(&vcon->port); |
int i = 0; |
break; |
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) |
static int generic_port_init(VirtConsole *vcon, VirtIOSerialDevice *dev) |
{ |
{ |
/* we will ignore any event for the time being */ |
vcon->port.info = dev->info; |
|
|
|
if (vcon->chr) { |
|
qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, |
|
vcon); |
|
vcon->port.info->have_data = flush_buf; |
|
} |
|
return 0; |
} |
} |
|
|
static void virtio_console_save(QEMUFile *f, void *opaque) |
/* Virtio Console Ports */ |
|
static int virtconsole_initfn(VirtIOSerialDevice *dev) |
{ |
{ |
VirtIOConsole *s = opaque; |
VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); |
|
VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); |
|
|
virtio_save(&s->vdev, f); |
port->is_console = true; |
|
return generic_port_init(vcon, dev); |
} |
} |
|
|
static int virtio_console_load(QEMUFile *f, void *opaque, int version_id) |
static int virtconsole_exitfn(VirtIOSerialDevice *dev) |
{ |
{ |
VirtIOConsole *s = opaque; |
VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); |
|
VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); |
|
|
if (version_id != 1) |
if (vcon->chr) { |
return -EINVAL; |
port->info->have_data = NULL; |
|
qemu_chr_close(vcon->chr); |
|
} |
|
|
virtio_load(&s->vdev, f); |
|
return 0; |
return 0; |
} |
} |
|
|
VirtIODevice *virtio_console_init(DeviceState *dev) |
static VirtIOSerialPortInfo virtconsole_info = { |
{ |
.qdev.name = "virtconsole", |
VirtIOConsole *s; |
.qdev.size = sizeof(VirtConsole), |
s = (VirtIOConsole *)virtio_common_init("virtio-console", |
.init = virtconsole_initfn, |
VIRTIO_ID_CONSOLE, |
.exit = virtconsole_exitfn, |
0, sizeof(VirtIOConsole)); |
.qdev.props = (Property[]) { |
s->vdev.get_features = virtio_console_get_features; |
DEFINE_PROP_UINT8("is_console", VirtConsole, port.is_console, 1), |
|
DEFINE_PROP_UINT32("nr", VirtConsole, port.id, VIRTIO_CONSOLE_BAD_ID), |
s->ivq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_input); |
DEFINE_PROP_CHR("chardev", VirtConsole, chr), |
s->dvq = virtio_add_queue(&s->vdev, 128, virtio_console_handle_output); |
DEFINE_PROP_STRING("name", VirtConsole, port.name), |
|
DEFINE_PROP_END_OF_LIST(), |
s->chr = qdev_init_chardev(dev); |
}, |
qemu_chr_add_handlers(s->chr, vcon_can_read, vcon_read, vcon_event, s); |
}; |
|
|
|
static void virtconsole_register(void) |
|
{ |
|
virtio_serial_port_qdev_register(&virtconsole_info); |
|
} |
|
device_init(virtconsole_register) |
|
|
|
/* Generic Virtio Serial Ports */ |
|
static int virtserialport_initfn(VirtIOSerialDevice *dev) |
|
{ |
|
VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); |
|
VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); |
|
|
|
return generic_port_init(vcon, dev); |
|
} |
|
|
|
static VirtIOSerialPortInfo virtserialport_info = { |
|
.qdev.name = "virtserialport", |
|
.qdev.size = sizeof(VirtConsole), |
|
.init = virtserialport_initfn, |
|
.exit = virtconsole_exitfn, |
|
.qdev.props = (Property[]) { |
|
DEFINE_PROP_UINT32("nr", VirtConsole, port.id, VIRTIO_CONSOLE_BAD_ID), |
|
DEFINE_PROP_CHR("chardev", VirtConsole, chr), |
|
DEFINE_PROP_STRING("name", VirtConsole, port.name), |
|
DEFINE_PROP_END_OF_LIST(), |
|
}, |
|
}; |
|
|
register_savevm("virtio-console", -1, 1, virtio_console_save, virtio_console_load, s); |
static void virtserialport_register(void) |
|
{ |
return &s->vdev; |
virtio_serial_port_qdev_register(&virtserialport_info); |
} |
} |
|
device_init(virtserialport_register) |