From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1ME4Pj-0007J8-ER for qemu-devel@nongnu.org; Tue, 09 Jun 2009 12:43:07 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1ME4Pe-00079o-D1 for qemu-devel@nongnu.org; Tue, 09 Jun 2009 12:43:06 -0400 Received: from [199.232.76.173] (port=49861 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1ME4Pc-00079N-Uz for qemu-devel@nongnu.org; Tue, 09 Jun 2009 12:43:01 -0400 Received: from mx2.redhat.com ([66.187.237.31]:35533) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1ME4Pc-0007vB-8b for qemu-devel@nongnu.org; Tue, 09 Jun 2009 12:43:00 -0400 From: Amit Shah Date: Tue, 9 Jun 2009 22:12:48 +0530 Message-Id: <1244565768-9103-4-git-send-email-amit.shah@redhat.com> In-Reply-To: <1244565768-9103-3-git-send-email-amit.shah@redhat.com> References: <1244565768-9103-1-git-send-email-amit.shah@redhat.com> <1244565768-9103-2-git-send-email-amit.shah@redhat.com> <1244565768-9103-3-git-send-email-amit.shah@redhat.com> Subject: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: anthony@codemonkey.ws Cc: Amit Shah , qemu-devel@nongnu.org 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 --- 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 + * + * 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 + * + * 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 +@item -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