[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest
From: |
Amit Shah |
Subject: |
[Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication |
Date: |
Tue, 9 Jun 2009 22:12:48 +0530 |
This interface presents a char device from which bits can be
sent and read.
Sample uses for such a device can be obtaining info from the
guest like the file systems used, apps installed, etc. for
offline usage and logged-in users, clipboard copy-paste, etc.
for online usage.
Signed-off-by: Amit Shah <address@hidden>
---
Makefile.target | 2 +-
hw/pc.c | 17 +++
hw/pci.h | 1 +
hw/virtio-pci.c | 15 +++
hw/virtio-serial.c | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/virtio-serial.h | 31 +++++
hw/virtio.h | 1 +
qemu-options.hx | 8 ++
sysemu.h | 11 ++
vl.c | 62 ++++++++++
10 files changed, 467 insertions(+), 1 deletions(-)
create mode 100644 hw/virtio-serial.c
create mode 100644 hw/virtio-serial.h
diff --git a/Makefile.target b/Makefile.target
index 27de4b9..d8ad787 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -497,7 +497,7 @@ OBJS=vl.o osdep.o monitor.o pci.o loader.o isa_mmio.o
machine.o \
gdbstub.o gdbstub-xml.o
# virtio has to be here due to weird dependency between PCI and virtio-net.
# need to fix this properly
-OBJS+=virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o
+OBJS+=virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o
virtio-serial.o
ifdef CONFIG_KVM
OBJS+=kvm.o kvm-all.o
endif
diff --git a/hw/pc.c b/hw/pc.c
index 0934778..b4136a0 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -37,6 +37,8 @@
#include "watchdog.h"
#include "smbios.h"
+void *virtio_serial_new_port(PCIDevice *dev, int idx, char *name);
+
/* output Bochs bios info messages */
//#define DEBUG_BIOS
@@ -1164,6 +1166,21 @@ static void pc_init1(ram_addr_t ram_size,
}
}
}
+
+ /* Add virtio serial devices */
+ if (pci_enabled && virtio_serial_index) {
+ void *dev;
+
+ dev = pci_create_simple(pci_bus, -1, "virtio-serial-pci");
+ if (!dev) {
+ fprintf(stderr, "qemu: could not create virtio serial pci
device\n");
+ exit(1);
+ }
+
+ for (i = 0; i < virtio_serial_index; i++) {
+ virtio_serial_new_port(dev, i, virtio_serial_names[i]);
+ }
+ }
}
static void pc_init_pci(ram_addr_t ram_size,
diff --git a/hw/pci.h b/hw/pci.h
index 0405837..ab06008 100644
--- a/hw/pci.h
+++ b/hw/pci.h
@@ -69,6 +69,7 @@ extern target_phys_addr_t pci_mem_base;
#define PCI_DEVICE_ID_VIRTIO_BLOCK 0x1001
#define PCI_DEVICE_ID_VIRTIO_BALLOON 0x1002
#define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003
+#define PCI_DEVICE_ID_VIRTIO_SERIAL 0x1004
typedef void PCIConfigWriteFunc(PCIDevice *pci_dev,
uint32_t address, uint32_t data, int len);
diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c
index c072423..30e56e1 100644
--- a/hw/virtio-pci.c
+++ b/hw/virtio-pci.c
@@ -334,6 +334,19 @@ static void virtio_balloon_init_pci(PCIDevice *pci_dev)
0x00);
}
+static void virtio_serial_init_pci(PCIDevice *pci_dev)
+{
+ VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev);
+ VirtIODevice *vdev;
+
+ vdev = virtio_serial_init(&pci_dev->qdev);
+ virtio_init_pci(proxy, vdev,
+ PCI_VENDOR_ID_REDHAT_QUMRANET,
+ PCI_DEVICE_ID_VIRTIO_SERIAL,
+ PCI_CLASS_COMMUNICATION_OTHER,
+ 0x00);
+}
+
static void virtio_pci_register_devices(void)
{
pci_qdev_register("virtio-blk-pci", sizeof(VirtIOPCIProxy),
@@ -344,6 +357,8 @@ static void virtio_pci_register_devices(void)
virtio_console_init_pci);
pci_qdev_register("virtio-balloon-pci", sizeof(VirtIOPCIProxy),
virtio_balloon_init_pci);
+ pci_qdev_register("virtio-serial-pci", sizeof(VirtIOPCIProxy),
+ virtio_serial_init_pci);
}
device_init(virtio_pci_register_devices)
diff --git a/hw/virtio-serial.c b/hw/virtio-serial.c
new file mode 100644
index 0000000..9ce06f0
--- /dev/null
+++ b/hw/virtio-serial.c
@@ -0,0 +1,320 @@
+/*
+ * Virtio serial interface
+ *
+ * This serial interface is a paravirtualised guest<->host
+ * communication channel for relaying short messages and events in
+ * either direction.
+ *
+ * There's support for multiple serial channels within one virtio PCI
+ * device to keep the guest PCI device count low.
+ *
+ * Copyright (C) 2009, Red Hat, Inc.
+ *
+ * Author(s): Amit Shah <address@hidden>
+ *
+ * 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 "pci.h"
+#include "monitor.h"
+#include "qemu-char.h"
+#include "virtio.h"
+#include "virtio-serial.h"
+#include "sysemu.h"
+
+typedef struct VirtIOSerial {
+ VirtIODevice *vdev;
+ VirtQueue *cvq;
+ struct VirtIOSerialPort *ports;
+} VirtIOSerial;
+
+typedef struct VirtIOSerialPort {
+ VirtIOSerial *virtserial;
+ VirtQueue *ivq, *ovq;
+ CharDriverState *hd;
+ char name[VIRTIO_SERIAL_NAME_MAX_LEN];
+} VirtIOSerialPort;
+
+VirtIOSerial virtio_serial;
+
+static char *get_port_name_from_idx(uint8_t idx)
+{
+ if (idx > virtio_serial_index)
+ return NULL;
+
+ return virtio_serial.ports[idx].name;
+}
+
+static VirtIOSerialPort *get_port_from_vq(VirtQueue *vq)
+{
+ int i;
+
+ for (i = 0; i < virtio_serial_index; i++) {
+ if (vq == virtio_serial.ports[i].ivq
+ || vq == virtio_serial.ports[i].ovq) {
+ return &virtio_serial.ports[i];
+ }
+ }
+ return NULL;
+}
+
+static void virtio_serial_handle_control(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtQueueElement elem;
+
+ while (virtqueue_pop(vq, &elem)) {
+ int i;
+ char *name;
+ uint8_t idx;
+ uint32_t key;
+ ssize_t len, strlen;
+
+ len = 0;
+ for (i = 0; i < elem.out_num; i++) {
+ memcpy(&key, elem.out_sg[i].iov_base, 4);
+
+ switch(key) {
+ case VIRTIO_SERIAL_GET_PORT_NAME:
+ memcpy(&idx, elem.out_sg[i].iov_base + 4, 1);
+ name = get_port_name_from_idx(idx);
+
+ strlen = strnlen(name, VIRTIO_SERIAL_NAME_MAX_LEN);
+
+ memcpy(elem.in_sg[0].iov_base, &key, 4);
+ memcpy(elem.in_sg[0].iov_base + 4, &idx, 1);
+ memcpy(elem.in_sg[0].iov_base + 5, name, strlen);
+ len = 5 + strlen;
+ break;
+ }
+ }
+ virtqueue_push(vq, &elem, len);
+ }
+ virtio_notify(vdev, vq);
+
+ return;
+}
+
+static void virtio_serial_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSerialPort *port = get_port_from_vq(vq);
+ VirtQueueElement elem;
+
+ while (virtqueue_pop(vq, &elem)) {
+ ssize_t len = 0;
+ int i;
+
+ if (port->hd) {
+ for (i = 0; i < elem.out_num; i++) {
+ len += qemu_chr_write(port->hd, elem.out_sg[i].iov_base,
+ elem.out_sg[i].iov_len);
+ }
+ }
+ virtqueue_push(vq, &elem, len);
+ }
+ virtio_notify(vdev, vq);
+}
+
+static void virtio_serial_handle_input(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+
+/* FIXME: we just accept a single string */
+void virtio_serial_send_input(const char *command, const char *key,
+ const char *value)
+{
+ VirtQueueElement elem;
+ VirtIOSerialPort *s = &virtio_serial.ports[0];
+ VirtIODevice *vdev = s->virtserial->vdev;
+ VirtQueue *vq = s->ivq;
+ char buf[300];
+ ssize_t len;
+ int ret;
+ unsigned int i;
+
+ if (!virtio_queue_ready(s->ivq)) {
+ goto queue_not_ready;
+ }
+
+ len = snprintf(buf, 299, "%s %s %s\n", command, key, value);
+
+ /* FIXME! actually handle this in a for loop */
+
+ ret = virtqueue_pop(vq, &elem);
+ if (!ret) {
+ goto queue_not_ready;
+ }
+
+ i = 0;
+ /* Note: We only have PAGE_SIZE sized buffers */
+ memcpy(elem.in_sg[i].iov_base, buf, len);
+ elem.in_sg[i].iov_len = len;
+
+ virtqueue_push(vq, &elem, len);
+ virtio_notify(vdev, vq);
+ return;
+
+queue_not_ready:
+ monitor_printf(cur_mon,
+ "vmserial: No free virtio buffer found. Message not
sent.\n");
+ return;
+}
+
+static int cons_can_read(void *opaque)
+{
+ VirtIOSerialPort *port = (VirtIOSerialPort *) opaque;
+
+ if (!virtio_queue_ready(port->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.
+ */
+ if (virtqueue_avail_bytes(port->ivq, TARGET_PAGE_SIZE, 0)) {
+ return TARGET_PAGE_SIZE;
+ }
+ if (virtqueue_avail_bytes(port->ivq, 1, 0)) {
+ return 1;
+ }
+ return 0;
+}
+
+static void cons_read(void *opaque, const uint8_t *buf, int size)
+{
+ VirtIOSerialPort *port = (VirtIOSerialPort *) 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(port->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(port->ivq, &elem, size);
+ }
+ virtio_notify(port->virtserial->vdev, port->ivq);
+}
+
+static void cons_event(void *opaque, int event)
+{
+ /* we will ignore any event for the time being */
+}
+
+static uint32_t virtio_serial_get_features(VirtIODevice *vdev)
+{
+ return 0;
+}
+
+static void virtio_serial_get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ struct virtio_serial_config config;
+
+ /* This might have to be updated for serial port hotplug */
+ config.nr_ports = virtio_serial_index;
+ config.status = 0;
+
+ memcpy(config_data, &config, sizeof(config));
+}
+
+static void virtio_serial_set_config(VirtIODevice *vdev,
+ const uint8_t *config_data)
+{
+ struct virtio_serial_config config;
+
+ memcpy(&config, config_data, sizeof(config));
+
+ /* Nothing to do as of now */
+}
+
+static void virtio_serial_save(QEMUFile *f, void *opaque)
+{
+ VirtIODevice *vdev = opaque;
+
+ virtio_save(vdev, f);
+}
+
+static int virtio_serial_load(QEMUFile *f, void *opaque, int version_id)
+{
+ VirtIODevice *vdev = opaque;
+
+ if (version_id != 1)
+ return -EINVAL;
+
+ virtio_load(vdev, f);
+ return 0;
+}
+
+void *virtio_serial_new_port(PCIDevice *dev, int idx, char *name)
+{
+ VirtIOSerialPort *port;
+
+ port = &virtio_serial.ports[idx];
+
+ port->virtserial = &virtio_serial;
+
+ memcpy(port->name, name, VIRTIO_SERIAL_NAME_MAX_LEN);
+
+ port->hd = qdev_init_chardev(&dev->qdev);
+ if (port->hd) {
+ qemu_chr_add_handlers(port->hd, cons_can_read, cons_read, cons_event,
+ port);
+ }
+
+ /* Add queue for host to guest transfers */
+ port->ivq = virtio_add_queue(port->virtserial->vdev, 2,
+ virtio_serial_handle_input);
+ /* Add queue for guest to host transfers */
+ port->ovq = virtio_add_queue(port->virtserial->vdev, 2,
+ virtio_serial_handle_output);
+
+ /* Send an update to the guest about this new port added */
+ virtio_notify_config(port->virtserial->vdev);
+ return port;
+}
+
+VirtIODevice *virtio_serial_init(DeviceState *dev)
+{
+ VirtIODevice *vdev;
+ int nr_ports = 4;
+
+ vdev = virtio_common_init("virtio-serial",
+ VIRTIO_ID_SERIAL,
+ sizeof(struct virtio_serial_config),
+ sizeof(VirtIODevice));
+ if (vdev == NULL)
+ return NULL;
+
+ virtio_serial.vdev = vdev;
+ vdev->get_config = virtio_serial_get_config;
+ vdev->set_config = virtio_serial_set_config;
+ vdev->get_features = virtio_serial_get_features;
+
+ /* Add a queue for control information transfer common to all
+ * serial ports
+ */
+ virtio_serial.cvq = virtio_add_queue(vdev, 2,
virtio_serial_handle_control);
+
+ /* Allocate space for the number of serial ports specified on the
+ * command line
+ */
+ virtio_serial.ports = qemu_mallocz(sizeof(VirtIOSerialPort) * nr_ports);
+
+ register_savevm("virtio-serial", -1, 1, virtio_serial_save,
+ virtio_serial_load, vdev);
+
+ return vdev;
+}
diff --git a/hw/virtio-serial.h b/hw/virtio-serial.h
new file mode 100644
index 0000000..7d46c1f
--- /dev/null
+++ b/hw/virtio-serial.h
@@ -0,0 +1,31 @@
+/*
+ * Virtio Serial Support
+ *
+ * Copyright (C) 2009, Red Hat, Inc.
+ *
+ * Author(s): Amit Shah <address@hidden>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+#ifndef _QEMU_VIRTIO_SERIAL_H
+#define _QEMU_VIRTIO_SERIAL_H
+
+/* The ID for virtio serial */
+#define VIRTIO_ID_SERIAL 7
+
+struct virtio_serial_config
+{
+ uint8_t nr_ports;
+ uint16_t status;
+} __attribute__((packed));
+
+#define VIRTIO_SERIAL_GET_PORT_NAME 1
+
+
+void *virtio_serial_new_port(PCIDevice *dev, int idx, char *name);
+void virtio_serial_send_input(const char *command,
+ const char *key, const char *value);
+
+#endif
diff --git a/hw/virtio.h b/hw/virtio.h
index 425727e..d2b50c3 100644
--- a/hw/virtio.h
+++ b/hw/virtio.h
@@ -151,5 +151,6 @@ VirtIODevice *virtio_blk_init(DeviceState *dev);
VirtIODevice *virtio_net_init(DeviceState *dev);
VirtIODevice *virtio_console_init(DeviceState *dev);
VirtIODevice *virtio_balloon_init(DeviceState *dev);
+VirtIODevice *virtio_serial_init(DeviceState *dev);
#endif
diff --git a/qemu-options.hx b/qemu-options.hx
index 87af798..857d8f1 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1523,6 +1523,14 @@ STEXI
Set virtio console.
ETEXI
+DEF("virtioserial", HAS_ARG, QEMU_OPTION_virtioserial, \
+ "-virtioserial c\n" \
+ " define virtio serial device\n")
+STEXI
address@hidden -virtserial @var{c}
+Set virtio serial device.
+ETEXI
+
DEF("show-cursor", 0, QEMU_OPTION_show_cursor, \
"-show-cursor show cursor\n")
STEXI
diff --git a/sysemu.h b/sysemu.h
index 658aeec..4f2b5af 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -229,6 +229,17 @@ extern CharDriverState *parallel_hds[MAX_PARALLEL_PORTS];
extern CharDriverState *virtcon_hds[MAX_VIRTIO_CONSOLES];
+/* virtio serial ports */
+
+#define MAX_VIRTIO_SERIAL_PORTS 4
+#define VIRTIO_SERIAL_NAME_MAX_LEN 30
+
+
+extern CharDriverState *virtio_serial_hds[MAX_VIRTIO_SERIAL_PORTS];
+extern char
virtio_serial_names[MAX_VIRTIO_SERIAL_PORTS][VIRTIO_SERIAL_NAME_MAX_LEN];
+extern int virtio_serial_index;
+
+
#define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR)
#ifdef NEED_CPU_H
diff --git a/vl.c b/vl.c
index fcf8532..4d31281 100644
--- a/vl.c
+++ b/vl.c
@@ -234,6 +234,9 @@ int no_quit = 0;
CharDriverState *serial_hds[MAX_SERIAL_PORTS];
CharDriverState *parallel_hds[MAX_PARALLEL_PORTS];
CharDriverState *virtcon_hds[MAX_VIRTIO_CONSOLES];
+CharDriverState *virtio_serial_hds[MAX_VIRTIO_SERIAL_PORTS];
+char virtio_serial_names[MAX_VIRTIO_SERIAL_PORTS][VIRTIO_SERIAL_NAME_MAX_LEN];
+int virtio_serial_index;
#ifdef TARGET_I386
int win2k_install_hack = 0;
int rtc_td_hack = 0;
@@ -4945,6 +4948,7 @@ int main(int argc, char **argv, char **envp)
int parallel_device_index;
const char *virtio_consoles[MAX_VIRTIO_CONSOLES];
int virtio_console_index;
+ const char *virtio_serials[MAX_VIRTIO_SERIAL_PORTS];
const char *loadvm = NULL;
QEMUMachine *machine;
const char *cpu_model;
@@ -5024,6 +5028,10 @@ int main(int argc, char **argv, char **envp)
virtio_consoles[i] = NULL;
virtio_console_index = 0;
+ for (i = 0; i < MAX_VIRTIO_SERIAL_PORTS; i++)
+ virtio_serials[i] = NULL;
+ virtio_serial_index = 0;
+
for (i = 0; i < MAX_NODES; i++) {
node_mem[i] = 0;
node_cpumask[i] = 0;
@@ -5453,6 +5461,14 @@ int main(int argc, char **argv, char **envp)
virtio_consoles[virtio_console_index] = optarg;
virtio_console_index++;
break;
+ case QEMU_OPTION_virtioserial:
+ if (virtio_serial_index >= MAX_VIRTIO_SERIAL_PORTS) {
+ fprintf(stderr, "qemu: too many virtio serial ports\n");
+ exit(1);
+ }
+ virtio_serials[virtio_serial_index] = optarg;
+ virtio_serial_index++;
+ break;
case QEMU_OPTION_parallel:
if (parallel_device_index >= MAX_PARALLEL_PORTS) {
fprintf(stderr, "qemu: too many parallel ports\n");
@@ -6038,6 +6054,43 @@ int main(int argc, char **argv, char **envp)
}
}
+ for (i = 0; i < virtio_serial_index; i++) {
+ const char *virtseropt;
+ char devname[80];
+ int j, k;
+
+ memset(devname, 0, 80);
+ j = k = 0;
+ while (isalnum(virtio_serials[i][j])) {
+ devname[k] = virtio_serials[i][j];
+ k++;
+ j++;
+ }
+
+ if (devname[0] && strncmp(devname, "none", 4)) {
+ char label[32];
+ snprintf(label, sizeof(label), "virtio-serial%d", i);
+ virtio_serial_hds[i] = qemu_chr_open(label, devname, NULL);
+ if (!virtio_serial_hds[i]) {
+ fprintf(stderr, "qemu: could not open virtio serial '%s'\n",
+ devname);
+ exit(1);
+ }
+ }
+ virtseropt = strstr(virtio_serials[i], ",name=");
+ if (virtseropt) {
+ int j, k = 6;
+
+ for (j = 0; j < VIRTIO_SERIAL_NAME_MAX_LEN &&
isalnum(virtseropt[k]);
+ j++, k++) {
+ virtio_serial_names[i][j] = virtseropt[k];
+ }
+ if (j < VIRTIO_SERIAL_NAME_MAX_LEN - 1) {
+ virtio_serial_names[i][j + 1] = 0;
+ }
+ }
+ }
+
module_call_init(MODULE_INIT_DEVICE);
machine->init(ram_size, boot_devices,
@@ -6172,6 +6225,15 @@ int main(int argc, char **argv, char **envp)
}
}
+ for(i = 0; i < MAX_VIRTIO_SERIAL_PORTS; i++) {
+ const char *devname = virtio_serials[i];
+ if (virtio_serial_hds[i] && devname) {
+ if (strstart(devname, "vc", 0)) {
+ qemu_chr_printf(virtio_serial_hds[i], "virtio serial%d\r\n",
i);
+ }
+ }
+ }
+
if (gdbstub_dev && gdbserver_start(gdbstub_dev) < 0) {
fprintf(stderr, "qemu: could not open gdbserver on device '%s'\n",
gdbstub_dev);
--
1.6.0.6
- [Qemu-devel] [RFC] virtio-serial device, Amit Shah, 2009/06/09
- [Qemu-devel] [PATCH] pci: add define for communication class devices, Amit Shah, 2009/06/09
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Richard W.M. Jones, 2009/06/09
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Stuart Brady, 2009/06/09
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Anthony Liguori, 2009/06/09
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Paul Brook, 2009/06/09
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Amit Shah, 2009/06/10
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Jamie Lokier, 2009/06/10
- Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication, Amit Shah, 2009/06/11