From af595fab3adeff06543efb28527c7843745f3882 Mon Sep 17 00:00:00 2001 From: Eduardo Habkost <ehabkost@redhat.com> Date: Thu, 29 Jan 2009 15:03:21 -0200 Subject: [PATCH 33/54] Add vmchannel support again Revert "kvm: qemu: remove hypercall device" This reverts commit 1b1d6d0629d022dbe95fae5ab450be585f73e41d. Conflicts: qemu/Makefile.target qemu/vl.c --- qemu/Makefile.target | 3 + qemu/hw/hypercall.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++ qemu/hw/hypercall.h | 45 ++++++ qemu/hw/pc.c | 4 + qemu/sysemu.h | 6 + qemu/vl.c | 60 +++++++++ 6 files changed, 480 insertions(+), 0 deletions(-) create mode 100644 qemu/hw/hypercall.c create mode 100644 qemu/hw/hypercall.h diff --git a/qemu/Makefile.target b/qemu/Makefile.target index 806fdb5..eaec68e 100644 --- a/qemu/Makefile.target +++ b/qemu/Makefile.target @@ -699,6 +699,9 @@ OBJS += pcnet.o OBJS += rtl8139.o OBJS += e1000.o +# PCI Hypercall +OBJS+= hypercall.o + OBJS += device-hotplug.o ifeq ($(USE_KVM_DEVICE_ASSIGNMENT), 1) diff --git a/qemu/hw/hypercall.c b/qemu/hw/hypercall.c new file mode 100644 index 0000000..73f6bb1 --- /dev/null +++ b/qemu/hw/hypercall.c @@ -0,0 +1,362 @@ +/* + * QEMU-KVM Hypercall emulation + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2006 Qumranet + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "sysemu.h" +#include "qemu-char.h" +#include "hw/isa.h" +#include "hw/irq.h" +#include "hw/pci.h" +#include "hypercall.h" +#include <stddef.h> + +#define HYPERCALL_IOPORT_SIZE 0x100 + +static int use_hypercall_dev = 0; + +typedef struct VmChannelCharDriverState { + CharDriverState *vmchannel_hd; + uint32_t deviceid; +} VmChannelCharDriverState; + +static VmChannelCharDriverState vmchannel_hds[MAX_VMCHANNEL_DEVICES]; + +typedef struct HypercallState { + uint32_t hcr; + uint32_t hsr; + uint32_t txsize; + uint32_t txbuff; + uint32_t rxsize; + uint8_t RxBuff[HP_MEM_SIZE]; + uint8_t txbufferaccu[HP_MEM_SIZE]; + int txbufferaccu_offset; + int irq; + PCIDevice *pci_dev; + uint32_t index; +} HypercallState; + +static HypercallState *pHypercallStates[MAX_VMCHANNEL_DEVICES] = {NULL}; + +//#define HYPERCALL_DEBUG 1 + +static void hp_reset(HypercallState *s) +{ + s->hcr = HCR_DI; + s->hsr = 0; + s->txsize = 0; + s->txbuff = 0; + s->rxsize= 0; + s->txbufferaccu_offset = 0; +} + +static void hypercall_update_irq(HypercallState *s); + + +static void hp_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + HypercallState *s = opaque; + +#ifdef HYPERCALL_DEBUG + printf("%s: addr=0x%x, val=0x%x\n", __FUNCTION__, addr, val); +#endif + addr &= 0xff; + + switch(addr) + { + case HCR_REGISTER: + { + s->hcr = val; + if (s->hcr & HCR_DI) + hypercall_update_irq(s); + if (val & HCR_GRS){ + hp_reset(s); + } + break; + } + + case HP_TXSIZE: + { + // handle the case when the we are being called when txsize is not 0 + if (s->txsize != 0) { + printf("txsize is being set, but txsize is not 0!!!\n"); + } + if (val > HP_MEM_SIZE) { + printf("txsize is larger than allowed by hw!!!\n"); + } + s->txsize = val; + s->txbufferaccu_offset = 0; + break; + } + + case HP_TXBUFF: + { + if (s->txsize == 0) { + printf("error with txbuff!!!\n"); + break; + } + + s->txbufferaccu[s->txbufferaccu_offset] = val; + s->txbufferaccu_offset++; + if (s->txbufferaccu_offset >= s->txsize) { + qemu_chr_write(vmchannel_hds[s->index].vmchannel_hd, s->txbufferaccu, s->txsize); + s->txbufferaccu_offset = 0; + s->txsize = 0; + } + break; + } + default: + { + printf("hp_ioport_write to unhandled address!!!\n"); + } + } +} + +static uint32_t hp_ioport_read(void *opaque, uint32_t addr) +{ + HypercallState *s = opaque; + int ret; + + addr &= 0xff; +#ifdef HYPERCALL_DEBUG + // Since HSR_REGISTER is being repeatedly read in the guest ISR we don't print it + if (addr != HSR_REGISTER) + printf("%s: addr=0x%x", __FUNCTION__, addr); +#endif + + if (addr >= offsetof(HypercallState, RxBuff) ) + { + int RxBuffOffset = addr - (offsetof(HypercallState, RxBuff)); + ret = s->RxBuff[RxBuffOffset]; +#ifdef HYPERCALL_DEBUG + printf(" val=%x\n", ret); +#endif + return ret; + } + + switch (addr) + { + case HSR_REGISTER: + ret = s->hsr; + if (ret & HSR_VDR) { + s->hsr &= ~HSR_VDR; + } + break; + case HP_RXSIZE: + ret = s->rxsize; + break; + + default: + ret = 0x00; + break; + } +#ifdef HYPERCALL_DEBUG + printf(" val=%x\n", ret); +#endif + return ret; +} + +/***********************************************************/ +/* PCI Hypercall definitions */ + +typedef struct PCIHypercallState { + PCIDevice dev; + HypercallState hp; +} PCIHypercallState; + +static void hp_map(PCIDevice *pci_dev, int region_num, + uint32_t addr, uint32_t size, int type) +{ + PCIHypercallState *d = (PCIHypercallState *)pci_dev; + HypercallState *s = &d->hp; + + register_ioport_write(addr, HYPERCALL_IOPORT_SIZE, 1, hp_ioport_write, s); + register_ioport_read(addr, HYPERCALL_IOPORT_SIZE, 1, hp_ioport_read, s); + +} + + +static void hypercall_update_irq(HypercallState *s) +{ + /* PCI irq */ + qemu_set_irq(s->pci_dev->irq[0], !(s->hcr & HCR_DI)); +} + +static void hc_save(QEMUFile* f,void* opaque) +{ + HypercallState* s=(HypercallState*)opaque; + + pci_device_save(s->pci_dev, f); + + qemu_put_be32s(f, &s->hcr); + qemu_put_be32s(f, &s->hsr); + qemu_put_be32s(f, &s->txsize); + qemu_put_be32s(f, &s->txbuff); + qemu_put_be32s(f, &s->rxsize); + qemu_put_buffer(f, s->RxBuff, HP_MEM_SIZE); + qemu_put_buffer(f, s->txbufferaccu, HP_MEM_SIZE); + qemu_put_be32s(f, &s->txbufferaccu_offset); + qemu_put_be32s(f, &s->irq); + qemu_put_be32s(f, &s->index); + +} + +static int hc_load(QEMUFile* f,void* opaque,int version_id) +{ + HypercallState* s=(HypercallState*)opaque; + int ret; + + if (version_id != 1) + return -EINVAL; + + ret = pci_device_load(s->pci_dev, f); + if (ret < 0) + return ret; + + qemu_get_be32s(f, &s->hcr); + qemu_get_be32s(f, &s->hsr); + qemu_get_be32s(f, &s->txsize); + qemu_get_be32s(f, &s->txbuff); + qemu_get_be32s(f, &s->rxsize); + qemu_get_buffer(f, s->RxBuff, HP_MEM_SIZE); + qemu_get_buffer(f, s->txbufferaccu, HP_MEM_SIZE); + qemu_get_be32s(f, &s->txbufferaccu_offset); + qemu_get_be32s(f, &s->irq); + qemu_get_be32s(f, &s->index); + + return 0; +} + +static void pci_hypercall_single_init(PCIBus *bus, uint32_t deviceid, uint32_t index) +{ + PCIHypercallState *d; + HypercallState *s; + uint8_t *pci_conf; + char name[sizeof("HypercallX")]; + + sprintf(name, "Hypercall%d", index); + +#ifdef HYPERCALL_DEBUG + printf("%s, devicename:%s\n", __FUNCTION__, name); +#endif + + // If the vmchannel wasn't initialized, we don't want the Hypercall device in the guest + if (use_hypercall_dev == 0) { + return; + } + + d = (PCIHypercallState *)pci_register_device(bus, + name, sizeof(PCIHypercallState), + -1, + NULL, NULL); + + pci_conf = d->dev.config; + pci_conf[0x00] = 0x02; // Qumranet vendor ID 0x5002 + pci_conf[0x01] = 0x50; + pci_conf[0x02] = deviceid & 0x00ff; + pci_conf[0x03] = (deviceid & 0xff00) >> 8; + + pci_conf[0x09] = 0x00; // ProgIf + pci_conf[0x0a] = 0x00; // SubClass + pci_conf[0x0b] = 0x05; // BaseClass + + pci_conf[0x0e] = 0x00; // header_type + pci_conf[0x3d] = 1; // interrupt pin 0 + + pci_register_io_region(&d->dev, 0, HYPERCALL_IOPORT_SIZE, + PCI_ADDRESS_SPACE_IO, hp_map); + s = &d->hp; + pHypercallStates[index] = s; + s->index = index; + s->irq = 16; /* PCI interrupt */ + s->pci_dev = (PCIDevice *)d; + + hp_reset(s); + register_savevm(name, index, 1, hc_save, hc_load, s); +} + +void pci_hypercall_init(PCIBus *bus) +{ + int i; + + // loop devices & call pci_hypercall_single_init with device id's + for(i = 0; i < MAX_VMCHANNEL_DEVICES; i++){ + if (vmchannel_hds[i].vmchannel_hd) { + pci_hypercall_single_init(bus, vmchannel_hds[i].deviceid, i); + } + } +} + +static int vmchannel_can_read(void *opaque) +{ + return 128; +} + +static void vmchannel_event(void *opaque, int event) +{ + +#ifdef HYPERCALL_DEBUG + // if index is to be used outside the printf, take it out of the #ifdef block! + long index = (long)opaque; + printf("%s index:%ld, got event %i\n", __FUNCTION__, index, event); +#endif + + return; +} + +// input from vmchannel outside caller +static void vmchannel_read(void *opaque, const uint8_t *buf, int size) +{ + int i; + long index = (long)opaque; + +#ifdef HYPERCALL_DEBUG + printf("vmchannel_read buf size:%d\n", size); +#endif + + // if the hypercall device is in interrupts disabled state, don't accept the data + if (pHypercallStates[index]->hcr & HCR_DI) { + return; + } + + for(i = 0; i < size; i++) { + pHypercallStates[index]->RxBuff[i] = buf[i]; + } + pHypercallStates[index]->rxsize = size; + pHypercallStates[index]->hsr = HSR_VDR; + hypercall_update_irq(pHypercallStates[index]); +} + +void vmchannel_init(CharDriverState *hd, uint32_t deviceid, uint32_t index) +{ +#ifdef HYPERCALL_DEBUG + printf("vmchannel_init, index=%d, deviceid=0x%x\n", index, deviceid); +#endif + + vmchannel_hds[index].deviceid = deviceid; + vmchannel_hds[index].vmchannel_hd = hd; + + use_hypercall_dev = 1; + qemu_chr_add_handlers(vmchannel_hds[index].vmchannel_hd, vmchannel_can_read, vmchannel_read, + vmchannel_event, (void *)(long)index); +} diff --git a/qemu/hw/hypercall.h b/qemu/hw/hypercall.h new file mode 100644 index 0000000..97434a7 --- /dev/null +++ b/qemu/hw/hypercall.h @@ -0,0 +1,45 @@ +/* + * QEMU-KVM Hypercall emulation + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2006 Qumranet + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define HCR_REGISTER 0x00 // Hypercall Command Register WR +#define HSR_REGISTER 0x04 // Hypercall Status Register RD +#define HP_TXSIZE 0x08 +#define HP_TXBUFF 0x0c +#define HP_RXSIZE 0x10 +#define HP_RXBUFF 0x14 + +// HCR_REGISTER commands +#define HCR_DI 1 // disable interrupts +#define HCR_EI 2 // enable interrupts +#define HCR_GRS 4 // Global reset +#define HCR_RESET (HCR_GRS|HCR_DI) + + +// Bits in HSR_REGISTER +#define HSR_VDR 0x01 // vmchannel Data is ready to be read + +#define HP_MEM_SIZE 0xE0 + + diff --git a/qemu/hw/pc.c b/qemu/hw/pc.c index 52e17fd..951c4f4 100644 --- a/qemu/hw/pc.c +++ b/qemu/hw/pc.c @@ -1084,6 +1084,10 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size, } qemu_system_hot_add_init(cpu_model); +#define USE_HYPERCALL +#ifdef USE_HYPERCALL + pci_hypercall_init(pci_bus); +#endif if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { fprintf(stderr, "qemu: too many IDE bus\n"); diff --git a/qemu/sysemu.h b/qemu/sysemu.h index 027b8e7..f4e62fc 100644 --- a/qemu/sysemu.h +++ b/qemu/sysemu.h @@ -181,6 +181,12 @@ void drive_hot_add(int pcibus, const char *devfn_string, const char *opts); void device_hot_remove(int pcibus, int slot); void device_hot_remove_success(int pcibus, int slot); +/* vmchannel devices */ + +#define MAX_VMCHANNEL_DEVICES 4 +void pci_hypercall_init(PCIBus *bus); +void vmchannel_init(CharDriverState *hd, uint32_t deviceid, uint32_t index); + /* serial ports */ #define MAX_SERIAL_PORTS 4 diff --git a/qemu/vl.c b/qemu/vl.c index 25c97d1..8bdfd7e 100644 --- a/qemu/vl.c +++ b/qemu/vl.c @@ -218,6 +218,8 @@ static int full_screen = 0; static int no_frame = 0; #endif int no_quit = 0; +int balloon_used = 0; +CharDriverState *vmchannel_hds[MAX_VMCHANNEL_DEVICES]; CharDriverState *serial_hds[MAX_SERIAL_PORTS]; CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; #ifdef TARGET_I386 @@ -4121,6 +4123,8 @@ static void help(int exitcode) "\n" "Debug/Expert options:\n" "-monitor dev redirect the monitor to char device 'dev'\n" + "-vmchannel di:DI,dev redirect the hypercall device with device id DI, to char device 'dev'\n" + "-balloon dev redirect the balloon hypercall device to char device 'dev'\n" "-serial dev redirect the serial port to char device 'dev'\n" "-parallel dev redirect the parallel port to char device 'dev'\n" "-pidfile file Write PID to 'file'\n" @@ -4256,6 +4260,8 @@ enum { QEMU_OPTION_vga, QEMU_OPTION_echr, QEMU_OPTION_monitor, + QEMU_OPTION_balloon, + QEMU_OPTION_vmchannel, QEMU_OPTION_serial, QEMU_OPTION_parallel, QEMU_OPTION_loadvm, @@ -4394,6 +4400,7 @@ static const QEMUOption qemu_options[] = { #endif { "localtime", 0, QEMU_OPTION_localtime }, { "vga", HAS_ARG, QEMU_OPTION_vga }, + { "vmchannel", 1, QEMU_OPTION_vmchannel }, { "echr", HAS_ARG, QEMU_OPTION_echr }, { "monitor", HAS_ARG, QEMU_OPTION_monitor }, { "serial", HAS_ARG, QEMU_OPTION_serial }, @@ -4839,6 +4846,8 @@ int main(int argc, char **argv, char **envp) const char *monitor_device; const char *serial_devices[MAX_SERIAL_PORTS]; int serial_device_index; + char vmchannel_devices[MAX_VMCHANNEL_DEVICES][128]; + int vmchannel_device_index; const char *parallel_devices[MAX_PARALLEL_PORTS]; int parallel_device_index; const char *loadvm = NULL; @@ -4905,6 +4914,10 @@ int main(int argc, char **argv, char **envp) translation = BIOS_ATA_TRANSLATION_AUTO; monitor_device = "vc"; + for(i = 0; i < MAX_VMCHANNEL_DEVICES; i++) + vmchannel_devices[i][0] = '\0'; + vmchannel_device_index = 0; + serial_devices[0] = "vc:80Cx24C"; for(i = 1; i < MAX_SERIAL_PORTS; i++) serial_devices[i] = NULL; @@ -5294,6 +5307,28 @@ int main(int argc, char **argv, char **envp) case QEMU_OPTION_monitor: monitor_device = optarg; break; + case QEMU_OPTION_balloon: + if (vmchannel_device_index >= MAX_VMCHANNEL_DEVICES) { + fprintf(stderr, "qemu: too many balloon/vmchannel devices\n"); + exit(1); + } + if (balloon_used) { + fprintf(stderr, "qemu: only one balloon device can be used\n"); + exit(1); + } + sprintf(vmchannel_devices[vmchannel_device_index],"di:cdcd,%s", optarg); + vmchannel_device_index++; + balloon_used = 1; + break; + case QEMU_OPTION_vmchannel: + if (vmchannel_device_index >= MAX_VMCHANNEL_DEVICES) { + fprintf(stderr, "qemu: too many balloon/vmchannel devices\n"); + exit(1); + } + pstrcpy(vmchannel_devices[vmchannel_device_index], + sizeof(vmchannel_devices[0]), optarg); + vmchannel_device_index++; + break; case QEMU_OPTION_serial: if (serial_device_index >= MAX_SERIAL_PORTS) { fprintf(stderr, "qemu: too many serial ports\n"); @@ -5928,6 +5963,31 @@ int main(int argc, char **argv, char **envp) monitor_init(monitor_hd, !nographic); } + for(i = 0; i < MAX_VMCHANNEL_DEVICES; i++) { + const char *devname = vmchannel_devices[i]; + if (devname[0] != '\0' && strcmp(devname, "none")) { + int devid; + char *termn; + + if (strstart(devname, "di:", &devname)) { + devid = strtol(devname, &termn, 16); + devname = termn + 1; + } + else { + fprintf(stderr, "qemu: could not find vmchannel device id '%s'\n", + devname); + exit(1); + } + vmchannel_hds[i] = qemu_chr_open(devname); + if (!vmchannel_hds[i]) { + fprintf(stderr, "qemu: could not open vmchannel device '%s'\n", + devname); + exit(1); + } + vmchannel_init(vmchannel_hds[i], devid, i); + } + } + for(i = 0; i < MAX_SERIAL_PORTS; i++) { const char *devname = serial_devices[i]; if (devname && strcmp(devname, "none")) { -- 1.6.1