diff options
-rw-r--r-- | .gitmodules | 2 | ||||
-rw-r--r-- | Makefile.objs | 5 | ||||
-rw-r--r-- | Makefile.target | 6 | ||||
-rw-r--r-- | blockdev.c | 6 | ||||
-rwxr-xr-x | configure | 26 | ||||
-rw-r--r-- | hmp-commands.hx | 13 | ||||
-rw-r--r-- | hw/acpi_piix4.c | 79 | ||||
-rw-r--r-- | hw/device-assignment.c | 1969 | ||||
-rw-r--r-- | hw/device-assignment.h | 33 | ||||
-rw-r--r-- | hw/i8254_common.c | 2 | ||||
-rw-r--r-- | hw/msi.c | 127 | ||||
-rw-r--r-- | hw/msi.h | 1 | ||||
-rw-r--r-- | hw/msix.c | 200 | ||||
-rw-r--r-- | hw/msix.h | 2 | ||||
-rw-r--r-- | hw/pc.c | 22 | ||||
-rw-r--r-- | hw/pc.h | 7 | ||||
-rw-r--r-- | hw/pc_piix.c | 15 | ||||
-rw-r--r-- | hw/pci.c | 95 | ||||
-rw-r--r-- | hw/pci.h | 24 | ||||
-rw-r--r-- | hw/piix_pci.c | 10 | ||||
-rw-r--r-- | hw/testdev.c | 154 | ||||
-rw-r--r-- | hw/vga_int.h | 6 | ||||
-rw-r--r-- | hw/virtio-pci.c | 76 | ||||
-rw-r--r-- | kvm-all.c | 38 | ||||
-rw-r--r-- | kvm-stub.c | 43 | ||||
-rw-r--r-- | kvm.h | 30 | ||||
-rw-r--r-- | monitor.c | 21 | ||||
-rw-r--r-- | pc-bios/vgabios-cirrus.bin | bin | 35840 -> 35840 bytes | |||
-rw-r--r-- | pc-bios/vgabios-stdvga.bin | bin | 40448 -> 40448 bytes | |||
-rw-r--r-- | pc-bios/vgabios-vmware.bin | bin | 40448 -> 40448 bytes | |||
-rw-r--r-- | pc-bios/vgabios.bin | bin | 40448 -> 40448 bytes | |||
-rw-r--r-- | qemu-config.c | 4 | ||||
-rw-r--r-- | qemu-kvm.c | 317 | ||||
-rw-r--r-- | qemu-kvm.h | 112 | ||||
-rw-r--r-- | qemu-options.hx | 16 | ||||
m--------- | roms/vgabios | 0 | ||||
-rwxr-xr-x | scripts/qemu-kvm/make-release | 81 | ||||
-rw-r--r-- | sysemu.h | 3 | ||||
-rw-r--r-- | target-i386/cpu.h | 1 | ||||
-rw-r--r-- | vl.c | 35 |
40 files changed, 3553 insertions, 28 deletions
diff --git a/.gitmodules b/.gitmodules index eca876f85..533dcad8e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "roms/vgabios"] path = roms/vgabios - url = git://git.qemu.org/vgabios.git/ + url = git://git.kernel.org/pub/scm/virt/kvm/vgabios.git/ [submodule "roms/seabios"] path = roms/seabios url = git://git.qemu.org/seabios.git/ diff --git a/Makefile.objs b/Makefile.objs index 70c5c79a6..264f1fe5b 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -213,7 +213,7 @@ hw-obj-$(CONFIG_VIRTIO) += virtio-console.o hw-obj-y += usb/libhw.o hw-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o hw-obj-y += fw_cfg.o -hw-obj-$(CONFIG_PCI) += pci.o pci_bridge.o pci_bridge_dev.o +hw-obj-$(CONFIG_PCI) += pci_bridge.o pci_bridge_dev.o hw-obj-$(CONFIG_PCI) += msix.o msi.o hw-obj-$(CONFIG_PCI) += shpc.o hw-obj-$(CONFIG_PCI) += slotid_cap.o @@ -240,7 +240,8 @@ hw-obj-$(CONFIG_USB_OHCI) += usb/hcd-ohci.o hw-obj-$(CONFIG_USB_EHCI) += usb/hcd-ehci.o hw-obj-$(CONFIG_USB_XHCI) += usb/hcd-xhci.o hw-obj-$(CONFIG_FDC) += fdc.o -hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o +# needs fixes for cpu hotplug, so moved to Makefile.target: +# hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o hw-obj-$(CONFIG_I82374) += i82374.o diff --git a/Makefile.target b/Makefile.target index 15829041c..eda863764 100644 --- a/Makefile.target +++ b/Makefile.target @@ -183,6 +183,7 @@ obj-y = arch_init.o cpus.o monitor.o machine.o gdbstub.o balloon.o ioport.o # virtio has to be here due to weird dependency between PCI and virtio-net. # need to fix this properly obj-$(CONFIG_NO_PCI) += pci-stub.o +obj-$(CONFIG_PCI) += pci.o obj-$(CONFIG_VIRTIO) += virtio.o virtio-blk.o virtio-balloon.o virtio-net.o virtio-serial-bus.o obj-$(CONFIG_VIRTIO) += virtio-scsi.o obj-y += vhost_net.o @@ -230,6 +231,10 @@ obj-i386-y += pc_piix.o obj-i386-y += pc_sysfw.o obj-i386-$(CONFIG_KVM) += kvm/clock.o kvm/apic.o kvm/i8259.o kvm/ioapic.o kvm/i8254.o obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o +obj-i386-y += testdev.o +obj-i386-y += acpi.o acpi_piix4.o + +obj-i386-$(CONFIG_KVM_DEVICE_ASSIGNMENT) += device-assignment.o # shared objects obj-ppc-y = ppc.o ppc_booke.o @@ -285,6 +290,7 @@ obj-lm32-y += milkymist-vgafb.o obj-lm32-y += framebuffer.o obj-mips-y = mips_r4k.o mips_jazz.o mips_malta.o mips_mipssim.o +obj-mips-y += acpi.o acpi_piix4.o obj-mips-y += mips_addr.o mips_timer.o mips_int.o obj-mips-y += gt64xxx.o mc146818rtc.o obj-mips-$(CONFIG_FULONG) += bonito.o vt82c686.o mips_fulong2e.o diff --git a/blockdev.c b/blockdev.c index 67895b25d..0d20460f0 100644 --- a/blockdev.c +++ b/blockdev.c @@ -434,6 +434,12 @@ DriveInfo *drive_init(QemuOpts *opts, int default_to_scsi) return NULL; } + if (qemu_opt_get(opts, "boot") != NULL) { + fprintf(stderr, "qemu-kvm: boot=on|off is deprecated and will be " + "ignored. Future versions will reject this parameter. Please " + "update your scripts.\n"); + } + on_write_error = BLOCK_ERR_STOP_ENOSPC; if ((buf = qemu_opt_get(opts, "werror")) != NULL) { if (type != IF_IDE && type != IF_SCSI && type != IF_VIRTIO && type != IF_NONE) { @@ -102,7 +102,17 @@ cc_i386=i386-pc-linux-gnu-gcc libs_qga="" debug_info="yes" -target_list="" +target_list="x86_64-softmmu" + +kvm_version() { + local fname="$(dirname "$0")/KVM_VERSION" + + if test -f "$fname"; then + cat "$fname" + else + echo "qemu-kvm-devel" + fi +} # Default value for a variable defining feature "foo". # * foo="no" feature will only be used if --enable-foo arg is given @@ -177,9 +187,10 @@ bsd_user="no" guest_base="" uname_release="" mixemu="no" +kvm_cap_device_assignment="yes" aix="no" blobs="yes" -pkgversion="" +pkgversion=" ($(kvm_version))" pie="" zero_malloc="" trace_backend="nop" @@ -710,6 +721,10 @@ for opt do ;; --enable-tcg-interpreter) tcg_interpreter="yes" ;; + --disable-kvm-device-assignment) kvm_cap_device_assignment="no" + ;; + --enable-kvm-device-assignment) kvm_cap_device_assignment="yes" + ;; --disable-cap-ng) cap_ng="no" ;; --enable-cap-ng) cap_ng="yes" @@ -1065,6 +1080,8 @@ echo " --disable-slirp disable SLIRP userspace network connectivity" echo " --disable-kvm disable KVM acceleration support" echo " --enable-kvm enable KVM acceleration support" echo " --enable-tcg-interpreter enable TCG with bytecode interpreter (TCI)" +echo " --disable-kvm-device-assignment disable KVM device assignment support" +echo " --enable-kvm-device-assignment enable KVM device assignment support" echo " --disable-nptl disable usermode NPTL support" echo " --enable-nptl enable usermode NPTL support" echo " --enable-system enable all system emulation targets" @@ -3025,6 +3042,7 @@ echo "ATTR/XATTR support $attr" echo "Install blobs $blobs" echo "KVM support $kvm" echo "TCG interpreter $tcg_interpreter" +echo "KVM device assig. $kvm_cap_device_assignment" echo "fdt support $fdt" echo "preadv support $preadv" echo "fdatasync $fdatasync" @@ -3739,9 +3757,13 @@ case "$target_arch2" in \( "$target_arch2" = "x86_64" -a "$cpu" = "i386" \) -o \ \( "$target_arch2" = "i386" -a "$cpu" = "x86_64" \) \) ; then echo "CONFIG_KVM=y" >> $config_target_mak + echo "CONFIG_KVM_OPTIONS=y" >> $config_host_mak if test "$vhost_net" = "yes" ; then echo "CONFIG_VHOST_NET=y" >> $config_target_mak fi + if test $kvm_cap_device_assignment = "yes" ; then + echo "CONFIG_KVM_DEVICE_ASSIGNMENT=y" >> $config_target_mak + fi fi esac if test "$target_arch2" = "ppc64" -a "$fdt" = "yes"; then diff --git a/hmp-commands.hx b/hmp-commands.hx index 18cb415ac..969f51208 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -1268,6 +1268,19 @@ Set the encrypted device @var{device} password to @var{password} ETEXI { + .name = "cpu_set", + .args_type = "cpu:i,state:s", + .params = "cpu [online|offline]", + .help = "change cpu state", + .mhandler.cmd = do_cpu_set_nr, + }, + +STEXI +@item cpu_set @var{cpu} [online|offline] +Set CPU @var{cpu} online or offline. +ETEXI + + { .name = "set_password", .args_type = "protocol:s,password:s,connected:s?", .params = "protocol password action-if-connected", diff --git a/hw/acpi_piix4.c b/hw/acpi_piix4.c index 7bc4e0dba..106493537 100644 --- a/hw/acpi_piix4.c +++ b/hw/acpi_piix4.c @@ -39,14 +39,20 @@ #define ACPI_DBG_IO_ADDR 0xb044 #define GPE_BASE 0xafe0 +#define PROC_BASE 0xaf00 #define GPE_LEN 4 #define PCI_UP_BASE 0xae00 #define PCI_DOWN_BASE 0xae04 #define PCI_EJ_BASE 0xae08 #define PCI_RMV_BASE 0xae0c +#define PIIX4_CPU_HOTPLUG_STATUS 4 #define PIIX4_PCI_HOTPLUG_STATUS 2 +struct gpe_regs { + uint8_t cpus_sts[32]; +}; + struct pci_status { uint32_t up; /* deprecated, maintained for migration compatibility */ uint32_t down; @@ -68,6 +74,7 @@ typedef struct PIIX4PMState { Notifier machine_ready; /* for pci hotplug */ + struct gpe_regs gpe_cpu; struct pci_status pci0_status; uint32_t pci0_hotplug_enable; uint32_t pci0_slot_device_present; @@ -229,10 +236,9 @@ static int vmstate_acpi_post_load(void *opaque, int version_id) { \ .name = (stringify(_field)), \ .version_id = 0, \ - .num = GPE_LEN, \ .info = &vmstate_info_uint16, \ .size = sizeof(uint16_t), \ - .flags = VMS_ARRAY | VMS_POINTER, \ + .flags = VMS_SINGLE | VMS_POINTER, \ .offset = vmstate_offset_pointer(_state, _field, uint8_t), \ } @@ -376,11 +382,16 @@ static void piix4_pm_machine_ready(Notifier *n, void *opaque) } +static PIIX4PMState *global_piix4_pm_state; /* cpu hotadd */ + static int piix4_pm_initfn(PCIDevice *dev) { PIIX4PMState *s = DO_UPCAST(PIIX4PMState, dev, dev); uint8_t *pci_conf; + /* for cpu hotadd */ + global_piix4_pm_state = s; + pci_conf = s->dev.config; pci_conf[0x06] = 0x80; pci_conf[0x07] = 0x02; @@ -481,7 +492,16 @@ type_init(piix4_pm_register_types) static uint32_t gpe_readb(void *opaque, uint32_t addr) { PIIX4PMState *s = opaque; - uint32_t val = acpi_gpe_ioport_readb(&s->ar, addr); + uint32_t val = 0; + struct gpe_regs *g = &s->gpe_cpu; + + switch (addr) { + case PROC_BASE ... PROC_BASE+31: + val = g->cpus_sts[addr - PROC_BASE]; + break; + default: + val = acpi_gpe_ioport_readb(&s->ar, addr); + } PIIX4_DPRINTF("gpe read %x == %x\n", addr, val); return val; @@ -540,16 +560,27 @@ static uint32_t pcirmv_read(void *opaque, uint32_t addr) return s->pci0_hotplug_enable; } +extern const char *global_cpu_model; + static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev, PCIHotplugState state); static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s) { + int i = 0, cpus = smp_cpus; + + while (cpus > 0) { + s->gpe_cpu.cpus_sts[i++] = (cpus < 8) ? (1 << cpus) - 1 : 0xff; + cpus -= 8; + } register_ioport_write(GPE_BASE, GPE_LEN, 1, gpe_writeb, s); register_ioport_read(GPE_BASE, GPE_LEN, 1, gpe_readb, s); acpi_gpe_blk(&s->ar, GPE_BASE); + register_ioport_write(PROC_BASE, 32, 1, gpe_writeb, s); + register_ioport_read(PROC_BASE, 32, 1, gpe_readb, s); + register_ioport_read(PCI_UP_BASE, 4, 4, pci_up_read, s); register_ioport_read(PCI_DOWN_BASE, 4, 4, pci_down_read, s); @@ -561,6 +592,48 @@ static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s) pci_bus_hotplug(bus, piix4_device_hotplug, &s->dev.qdev); } +#if defined(TARGET_I386) +static void enable_processor(PIIX4PMState *s, int cpu) +{ + struct gpe_regs *g = &s->gpe_cpu; + ACPIGPE *gpe = &s->ar.gpe; + + *gpe->sts = *gpe->sts | PIIX4_CPU_HOTPLUG_STATUS; + g->cpus_sts[cpu/8] |= (1 << (cpu%8)); +} + +static void disable_processor(PIIX4PMState *s, int cpu) +{ + struct gpe_regs *g = &s->gpe_cpu; + ACPIGPE *gpe = &s->ar.gpe; + + *gpe->sts = *gpe->sts | PIIX4_CPU_HOTPLUG_STATUS; + g->cpus_sts[cpu/8] &= ~(1 << (cpu%8)); +} + +void qemu_system_cpu_hot_add(int cpu, int state) +{ + CPUArchState *env; + PIIX4PMState *s = global_piix4_pm_state; + + if (state && !qemu_get_cpu(cpu)) { + env = pc_new_cpu(global_cpu_model); + if (!env) { + fprintf(stderr, "cpu %d creation failed\n", cpu); + return; + } + env->cpuid_apic_id = cpu; + } + + if (state) + enable_processor(s, cpu); + else + disable_processor(s, cpu); + + pm_update_sci(s); +} +#endif + static void enable_device(PIIX4PMState *s, int slot) { s->ar.gpe.sts[0] |= PIIX4_PCI_HOTPLUG_STATUS; diff --git a/hw/device-assignment.c b/hw/device-assignment.c new file mode 100644 index 000000000..92710027e --- /dev/null +++ b/hw/device-assignment.c @@ -0,0 +1,1969 @@ +/* + * Copyright (c) 2007, Neocleus Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * + * Assign a PCI device from the host to a guest VM. + * + * Adapted for KVM by Qumranet. + * + * Copyright (c) 2007, Neocleus, Alex Novik (alex@neocleus.com) + * Copyright (c) 2007, Neocleus, Guy Zana (guy@neocleus.com) + * Copyright (C) 2008, Qumranet, Amit Shah (amit.shah@qumranet.com) + * Copyright (C) 2008, Red Hat, Amit Shah (amit.shah@redhat.com) + * Copyright (C) 2008, IBM, Muli Ben-Yehuda (muli@il.ibm.com) + */ +#include <stdio.h> +#include <unistd.h> +#include <sys/io.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "qemu-kvm.h" +#include "hw.h" +#include "pc.h" +#include "qemu-error.h" +#include "console.h" +#include "device-assignment.h" +#include "loader.h" +#include "monitor.h" +#include "range.h" +#include "sysemu.h" +#include "pci.h" + +#define MSIX_PAGE_SIZE 0x1000 + +/* From linux/ioport.h */ +#define IORESOURCE_IO 0x00000100 /* Resource type */ +#define IORESOURCE_MEM 0x00000200 +#define IORESOURCE_IRQ 0x00000400 +#define IORESOURCE_DMA 0x00000800 +#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */ + +/* #define DEVICE_ASSIGNMENT_DEBUG 1 */ + +#ifdef DEVICE_ASSIGNMENT_DEBUG +#define DEBUG(fmt, ...) \ + do { \ + fprintf(stderr, "%s: " fmt, __func__ , __VA_ARGS__); \ + } while (0) +#else +#define DEBUG(fmt, ...) do { } while(0) +#endif + +typedef struct PCIHostDevice { + int seg; + int bus; + int dev; + int func; +} PCIHostDevice; + +typedef struct { + int type; /* Memory or port I/O */ + int valid; + uint32_t base_addr; + uint32_t size; /* size of the region */ + int resource_fd; +} PCIRegion; + +typedef struct { + uint8_t bus, dev, func; /* Bus inside domain, device and function */ + int irq; /* IRQ number */ + uint16_t region_number; /* number of active regions */ + + /* Port I/O or MMIO Regions */ + PCIRegion regions[PCI_NUM_REGIONS - 1]; + int config_fd; +} PCIDevRegions; + +typedef struct { + MemoryRegion container; + MemoryRegion real_iomem; + union { + void *r_virtbase; /* mmapped access address for memory regions */ + uint32_t r_baseport; /* the base guest port for I/O regions */ + } u; + pcibus_t e_size; /* emulated size of region in bytes */ + pcibus_t r_size; /* real size of region in bytes */ + PCIRegion *region; +} AssignedDevRegion; + +#define ASSIGNED_DEVICE_PREFER_MSI_BIT 0 +#define ASSIGNED_DEVICE_SHARE_INTX_BIT 1 + +#define ASSIGNED_DEVICE_PREFER_MSI_MASK (1 << ASSIGNED_DEVICE_PREFER_MSI_BIT) +#define ASSIGNED_DEVICE_SHARE_INTX_MASK (1 << ASSIGNED_DEVICE_SHARE_INTX_BIT) + +typedef struct { + uint32_t addr_lo; + uint32_t addr_hi; + uint32_t data; + uint32_t ctrl; +} MSIXTableEntry; + +typedef struct AssignedDevice { + PCIDevice dev; + PCIHostDevice host; + uint32_t features; + int intpin; + uint8_t debug_flags; + AssignedDevRegion v_addrs[PCI_NUM_REGIONS - 1]; + PCIDevRegions real_device; + int run; + int girq; + uint16_t h_segnr; + uint8_t h_busnr; + uint8_t h_devfn; + int irq_requested_type; + int bound; + struct { +#define ASSIGNED_DEVICE_CAP_MSI (1 << 0) +#define ASSIGNED_DEVICE_CAP_MSIX (1 << 1) + uint32_t available; +#define ASSIGNED_DEVICE_MSI_ENABLED (1 << 0) +#define ASSIGNED_DEVICE_MSIX_ENABLED (1 << 1) +#define ASSIGNED_DEVICE_MSIX_MASKED (1 << 2) + uint32_t state; + } cap; + uint8_t emulate_config_read[PCI_CONFIG_SPACE_SIZE]; + uint8_t emulate_config_write[PCI_CONFIG_SPACE_SIZE]; + int irq_entries_nr; + struct kvm_irq_routing_entry *entry; + MSIXTableEntry *msix_table; + target_phys_addr_t msix_table_addr; + uint16_t msix_max; + MemoryRegion mmio; + char *configfd_name; + int32_t bootindex; + QLIST_ENTRY(AssignedDevice) next; +} AssignedDevice; + +static void assigned_dev_load_option_rom(AssignedDevice *dev); + +static void assigned_dev_unregister_msix_mmio(AssignedDevice *dev); + +static uint64_t assigned_dev_ioport_rw(AssignedDevRegion *dev_region, + target_phys_addr_t addr, int size, + uint64_t *data) +{ + uint64_t val = 0; + int fd = dev_region->region->resource_fd; + + if (fd >= 0) { + if (data) { + DEBUG("pwrite data=%lx, size=%d, e_phys=%lx, addr=%lx\n", + *data, size, addr, addr); + if (pwrite(fd, data, size, addr) != size) { + fprintf(stderr, "%s - pwrite failed %s\n", + __func__, strerror(errno)); + } + } else { + if (pread(fd, &val, size, addr) != size) { + fprintf(stderr, "%s - pread failed %s\n", + __func__, strerror(errno)); + val = (1UL << (size * 8)) - 1; + } + DEBUG("pread val=%lx, size=%d, e_phys=%lx, addr=%lx\n", + val, size, addr, addr); + } + } else { + uint32_t port = addr + dev_region->u.r_baseport; + + if (data) { + DEBUG("out data=%lx, size=%d, e_phys=%lx, host=%x\n", + *data, size, addr, port); + switch (size) { + case 1: + outb(*data, port); + break; + case 2: + outw(*data, port); + break; + case 4: + outl(*data, port); + break; + } + } else { + switch (size) { + case 1: + val = inb(port); + break; + case 2: + val = inw(port); + break; + case 4: + val = inl(port); + break; + } + DEBUG("in data=%lx, size=%d, e_phys=%lx, host=%x\n", + val, size, addr, port); + } + } + return val; +} + +static void assigned_dev_ioport_write(void *opaque, target_phys_addr_t addr, + uint64_t data, unsigned size) +{ + assigned_dev_ioport_rw(opaque, addr, size, &data); +} + +static uint64_t assigned_dev_ioport_read(void *opaque, + target_phys_addr_t addr, unsigned size) +{ + return assigned_dev_ioport_rw(opaque, addr, size, NULL); +} + +static uint32_t slow_bar_readb(void *opaque, target_phys_addr_t addr) +{ + AssignedDevRegion *d = opaque; + uint8_t *in = d->u.r_virtbase + addr; + uint32_t r; + + r = *in; + DEBUG("slow_bar_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r); + + return r; +} + +static uint32_t slow_bar_readw(void *opaque, target_phys_addr_t addr) +{ + AssignedDevRegion *d = opaque; + uint16_t *in = d->u.r_virtbase + addr; + uint32_t r; + + r = *in; + DEBUG("slow_bar_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r); + + return r; +} + +static uint32_t slow_bar_readl(void *opaque, target_phys_addr_t addr) +{ + AssignedDevRegion *d = opaque; + uint32_t *in = d->u.r_virtbase + addr; + uint32_t r; + + r = *in; + DEBUG("slow_bar_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, r); + + return r; +} + +static void slow_bar_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + AssignedDevRegion *d = opaque; + uint8_t *out = d->u.r_virtbase + addr; + + DEBUG("slow_bar_writeb addr=0x" TARGET_FMT_plx " val=0x%02x\n", addr, val); + *out = val; +} + +static void slow_bar_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + AssignedDevRegion *d = opaque; + uint16_t *out = d->u.r_virtbase + addr; + + DEBUG("slow_bar_writew addr=0x" TARGET_FMT_plx " val=0x%04x\n", addr, val); + *out = val; +} + +static void slow_bar_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + AssignedDevRegion *d = opaque; + uint32_t *out = d->u.r_virtbase + addr; + + DEBUG("slow_bar_writel addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, val); + *out = val; +} + +static const MemoryRegionOps slow_bar_ops = { + .old_mmio = { + .read = { slow_bar_readb, slow_bar_readw, slow_bar_readl, }, + .write = { slow_bar_writeb, slow_bar_writew, slow_bar_writel, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void assigned_dev_iomem_setup(PCIDevice *pci_dev, int region_num, + pcibus_t e_size) +{ + AssignedDevice *r_dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + AssignedDevRegion *region = &r_dev->v_addrs[region_num]; + PCIRegion *real_region = &r_dev->real_device.regions[region_num]; + + if (e_size > 0) { + memory_region_init(®ion->container, "assigned-dev-container", + e_size); + memory_region_add_subregion(®ion->container, 0, ®ion->real_iomem); + + /* deal with MSI-X MMIO page */ + if (real_region->base_addr <= r_dev->msix_table_addr && + real_region->base_addr + real_region->size > + r_dev->msix_table_addr) { + int offset = r_dev->msix_table_addr - real_region->base_addr; + + memory_region_add_subregion_overlap(®ion->container, + offset, + &r_dev->mmio, + 1); + } + } +} + +static const MemoryRegionOps assigned_dev_ioport_ops = { + .read = assigned_dev_ioport_read, + .write = assigned_dev_ioport_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void assigned_dev_ioport_setup(PCIDevice *pci_dev, int region_num, + pcibus_t size) +{ + AssignedDevice *r_dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + AssignedDevRegion *region = &r_dev->v_addrs[region_num]; + + region->e_size = size; + memory_region_init(®ion->container, "assigned-dev-container", size); + memory_region_init_io(®ion->real_iomem, &assigned_dev_ioport_ops, + r_dev->v_addrs + region_num, + "assigned-dev-iomem", size); + memory_region_add_subregion(®ion->container, 0, ®ion->real_iomem); +} + +static uint32_t assigned_dev_pci_read(PCIDevice *d, int pos, int len) +{ + AssignedDevice *pci_dev = DO_UPCAST(AssignedDevice, dev, d); + uint32_t val; + ssize_t ret; + int fd = pci_dev->real_device.config_fd; + +again: + ret = pread(fd, &val, len, pos); + if (ret != len) { + if ((ret < 0) && (errno == EINTR || errno == EAGAIN)) + goto again; + + fprintf(stderr, "%s: pread failed, ret = %zd errno = %d\n", + __func__, ret, errno); + + exit(1); + } + + return val; +} + +static uint8_t assigned_dev_pci_read_byte(PCIDevice *d, int pos) +{ + return (uint8_t)assigned_dev_pci_read(d, pos, 1); +} + +static void assigned_dev_pci_write(PCIDevice *d, int pos, uint32_t val, int len) +{ + AssignedDevice *pci_dev = DO_UPCAST(AssignedDevice, dev, d); + ssize_t ret; + int fd = pci_dev->real_device.config_fd; + +again: + ret = pwrite(fd, &val, len, pos); + if (ret != len) { + if ((ret < 0) && (errno == EINTR || errno == EAGAIN)) + goto again; + + fprintf(stderr, "%s: pwrite failed, ret = %zd errno = %d\n", + __func__, ret, errno); + + exit(1); + } + + return; +} + +static void assigned_dev_emulate_config_read(AssignedDevice *dev, + uint32_t offset, uint32_t len) +{ + memset(dev->emulate_config_read + offset, 0xff, len); +} + +static void assigned_dev_direct_config_read(AssignedDevice *dev, + uint32_t offset, uint32_t len) +{ + memset(dev->emulate_config_read + offset, 0, len); +} + +static void assigned_dev_direct_config_write(AssignedDevice *dev, + uint32_t offset, uint32_t len) +{ + memset(dev->emulate_config_write + offset, 0, len); +} + +static uint8_t pci_find_cap_offset(PCIDevice *d, uint8_t cap, uint8_t start) +{ + int id; + int max_cap = 48; + int pos = start ? start : PCI_CAPABILITY_LIST; + int status; + + status = assigned_dev_pci_read_byte(d, PCI_STATUS); + if ((status & PCI_STATUS_CAP_LIST) == 0) + return 0; + + while (max_cap--) { + pos = assigned_dev_pci_read_byte(d, pos); + if (pos < 0x40) + break; + + pos &= ~3; + id = assigned_dev_pci_read_byte(d, pos + PCI_CAP_LIST_ID); + + if (id == 0xff) + break; + if (id == cap) + return pos; + + pos += PCI_CAP_LIST_NEXT; + } + return 0; +} + +static int assigned_dev_register_regions(PCIRegion *io_regions, + unsigned long regions_num, + AssignedDevice *pci_dev) +{ + uint32_t i; + PCIRegion *cur_region = io_regions; + + for (i = 0; i < regions_num; i++, cur_region++) { + if (!cur_region->valid) + continue; + + /* handle memory io regions */ + if (cur_region->type & IORESOURCE_MEM) { + int t = cur_region->type & IORESOURCE_PREFETCH + ? PCI_BASE_ADDRESS_MEM_PREFETCH + : PCI_BASE_ADDRESS_SPACE_MEMORY; + + /* map physical memory */ + pci_dev->v_addrs[i].u.r_virtbase = mmap(NULL, cur_region->size, + PROT_WRITE | PROT_READ, + MAP_SHARED, + cur_region->resource_fd, + (off_t)0); + + if (pci_dev->v_addrs[i].u.r_virtbase == MAP_FAILED) { + pci_dev->v_addrs[i].u.r_virtbase = NULL; + fprintf(stderr, "%s: Error: Couldn't mmap 0x%x!" + "\n", __func__, + (uint32_t) (cur_region->base_addr)); + return -1; + } + + pci_dev->v_addrs[i].r_size = cur_region->size; + pci_dev->v_addrs[i].e_size = 0; + + /* add offset */ + pci_dev->v_addrs[i].u.r_virtbase += + (cur_region->base_addr & 0xFFF); + + if (cur_region->size & 0xFFF) { + fprintf(stderr, "PCI region %d at address 0x%llx " + "has size 0x%x, which is not a multiple of 4K. " + "You might experience some performance hit " + "due to that.\n", + i, (unsigned long long)cur_region->base_addr, + cur_region->size); + memory_region_init_io(&pci_dev->v_addrs[i].real_iomem, + &slow_bar_ops, &pci_dev->v_addrs[i], + "assigned-dev-slow-bar", + cur_region->size); + } else { + void *virtbase = pci_dev->v_addrs[i].u.r_virtbase; + char name[32]; + snprintf(name, sizeof(name), "%s.bar%d", + object_get_typename(OBJECT(pci_dev)), i); + memory_region_init_ram_ptr(&pci_dev->v_addrs[i].real_iomem, + name, cur_region->size, + virtbase); + vmstate_register_ram(&pci_dev->v_addrs[i].real_iomem, + &pci_dev->dev.qdev); + } + + assigned_dev_iomem_setup(&pci_dev->dev, i, cur_region->size); + pci_register_bar((PCIDevice *) pci_dev, i, t, + &pci_dev->v_addrs[i].container); + continue; + } else { + /* handle port io regions */ + uint32_t val; + int ret; + + /* Test kernel support for ioport resource read/write. Old + * kernels return EIO. New kernels only allow 1/2/4 byte reads + * so should return EINVAL for a 3 byte read */ + ret = pread(pci_dev->v_addrs[i].region->resource_fd, &val, 3, 0); + if (ret >= 0) { + fprintf(stderr, "Unexpected return from I/O port read: %d\n", + ret); + abort(); + } else if (errno != EINVAL) { + fprintf(stderr, "Kernel doesn't support ioport resource " + "access, hiding this region.\n"); + close(pci_dev->v_addrs[i].region->resource_fd); + cur_region->valid = 0; + continue; + } + + pci_dev->v_addrs[i].u.r_baseport = cur_region->base_addr; + pci_dev->v_addrs[i].r_size = cur_region->size; + pci_dev->v_addrs[i].e_size = 0; + + assigned_dev_ioport_setup(&pci_dev->dev, i, cur_region->size); + pci_register_bar((PCIDevice *) pci_dev, i, + PCI_BASE_ADDRESS_SPACE_IO, + &pci_dev->v_addrs[i].container); + } + } + + /* success */ + return 0; +} + +static int get_real_id(const char *devpath, const char *idname, uint16_t *val) +{ + FILE *f; + char name[128]; + long id; + + snprintf(name, sizeof(name), "%s%s", devpath, idname); + f = fopen(name, "r"); + if (f == NULL) { + fprintf(stderr, "%s: %s: %m\n", __func__, name); + return -1; + } + if (fscanf(f, "%li\n", &id) == 1) { + *val = id; + } else { + return -1; + } + fclose(f); + + return 0; +} + +static int get_real_vendor_id(const char *devpath, uint16_t *val) +{ + return get_real_id(devpath, "vendor", val); +} + +static int get_real_device_id(const char *devpath, uint16_t *val) +{ + return get_real_id(devpath, "device", val); +} + +static int get_real_device(AssignedDevice *pci_dev, uint16_t r_seg, + uint8_t r_bus, uint8_t r_dev, uint8_t r_func) +{ + char dir[128], name[128]; + int fd, r = 0, v; + FILE *f; + unsigned long long start, end, size, flags; + uint16_t id; + PCIRegion *rp; + PCIDevRegions *dev = &pci_dev->real_device; + + dev->region_number = 0; + + snprintf(dir, sizeof(dir), "/sys/bus/pci/devices/%04x:%02x:%02x.%x/", + r_seg, r_bus, r_dev, r_func); + + snprintf(name, sizeof(name), "%sconfig", dir); + + if (pci_dev->configfd_name && *pci_dev->configfd_name) { + if (qemu_isdigit(pci_dev->configfd_name[0])) { + dev->config_fd = strtol(pci_dev->configfd_name, NULL, 0); + } else { + dev->config_fd = monitor_get_fd(cur_mon, pci_dev->configfd_name); + if (dev->config_fd < 0) { + fprintf(stderr, "%s: (%s) unkown\n", __func__, + pci_dev->configfd_name); + return 1; + } + } + } else { + dev->config_fd = open(name, O_RDWR); + + if (dev->config_fd == -1) { + fprintf(stderr, "%s: %s: %m\n", __func__, name); + return 1; + } + } +again: + r = read(dev->config_fd, pci_dev->dev.config, + pci_config_size(&pci_dev->dev)); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + goto again; + fprintf(stderr, "%s: read failed, errno = %d\n", __func__, errno); + } + + /* Restore or clear multifunction, this is always controlled by qemu */ + if (pci_dev->dev.cap_present & QEMU_PCI_CAP_MULTIFUNCTION) { + pci_dev->dev.config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION; + } else { + pci_dev->dev.config[PCI_HEADER_TYPE] &= ~PCI_HEADER_TYPE_MULTI_FUNCTION; + } + + /* Clear host resource mapping info. If we choose not to register a + * BAR, such as might be the case with the option ROM, we can get + * confusing, unwritable, residual addresses from the host here. */ + memset(&pci_dev->dev.config[PCI_BASE_ADDRESS_0], 0, 24); + memset(&pci_dev->dev.config[PCI_ROM_ADDRESS], 0, 4); + + snprintf(name, sizeof(name), "%sresource", dir); + + f = fopen(name, "r"); + if (f == NULL) { + fprintf(stderr, "%s: %s: %m\n", __func__, name); + return 1; + } + + for (r = 0; r < PCI_ROM_SLOT; r++) { + if (fscanf(f, "%lli %lli %lli\n", &start, &end, &flags) != 3) + break; + + rp = dev->regions + r; + rp->valid = 0; + rp->resource_fd = -1; + size = end - start + 1; + flags &= IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH; + if (size == 0 || (flags & ~IORESOURCE_PREFETCH) == 0) + continue; + if (flags & IORESOURCE_MEM) { + flags &= ~IORESOURCE_IO; + } else { + flags &= ~IORESOURCE_PREFETCH; + } + snprintf(name, sizeof(name), "%sresource%d", dir, r); + fd = open(name, O_RDWR); + if (fd == -1) + continue; + rp->resource_fd = fd; + + rp->type = flags; + rp->valid = 1; + rp->base_addr = start; + rp->size = size; + pci_dev->v_addrs[r].region = rp; + DEBUG("region %d size %d start 0x%llx type %d resource_fd %d\n", + r, rp->size, start, rp->type, rp->resource_fd); + } + + fclose(f); + + /* read and fill vendor ID */ + v = get_real_vendor_id(dir, &id); + if (v) { + return 1; + } + pci_dev->dev.config[0] = id & 0xff; + pci_dev->dev.config[1] = (id & 0xff00) >> 8; + + /* read and fill device ID */ + v = get_real_device_id(dir, &id); + if (v) { + return 1; + } + pci_dev->dev.config[2] = id & 0xff; + pci_dev->dev.config[3] = (id & 0xff00) >> 8; + + pci_word_test_and_clear_mask(pci_dev->emulate_config_write + PCI_COMMAND, + PCI_COMMAND_MASTER | PCI_COMMAND_INTX_DISABLE); + + dev->region_number = r; + return 0; +} + +static QLIST_HEAD(, AssignedDevice) devs = QLIST_HEAD_INITIALIZER(devs); + +static void free_dev_irq_entries(AssignedDevice *dev) +{ + int i; + + for (i = 0; i < dev->irq_entries_nr; i++) { + if (dev->entry[i].type) { + kvm_del_routing_entry(&dev->entry[i]); + } + } + g_free(dev->entry); + dev->entry = NULL; + dev->irq_entries_nr = 0; +} + +static void free_assigned_device(AssignedDevice *dev) +{ + int i; + + if (dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) { + assigned_dev_unregister_msix_mmio(dev); + } + for (i = 0; i < dev->real_device.region_number; i++) { + PCIRegion *pci_region = &dev->real_device.regions[i]; + AssignedDevRegion *region = &dev->v_addrs[i]; + + if (!pci_region->valid) { + continue; + } + if (pci_region->type & IORESOURCE_IO) { + memory_region_del_subregion(®ion->container, + ®ion->real_iomem); + memory_region_destroy(®ion->real_iomem); + memory_region_destroy(®ion->container); + } else if (pci_region->type & IORESOURCE_MEM) { + if (region->u.r_virtbase) { + memory_region_del_subregion(®ion->container, + ®ion->real_iomem); + + /* Remove MSI-X table subregion */ + if (pci_region->base_addr <= dev->msix_table_addr && + pci_region->base_addr + pci_region->size > + dev->msix_table_addr) { + memory_region_del_subregion(®ion->container, + &dev->mmio); + } + + memory_region_destroy(®ion->real_iomem); + memory_region_destroy(®ion->container); + if (munmap(region->u.r_virtbase, + (pci_region->size + 0xFFF) & 0xFFFFF000)) { + fprintf(stderr, + "Failed to unmap assigned device region: %s\n", + strerror(errno)); + } + } + } + if (pci_region->resource_fd >= 0) { + close(pci_region->resource_fd); + } + } + + if (dev->real_device.config_fd >= 0) { + close(dev->real_device.config_fd); + } + + free_dev_irq_entries(dev); +} + +static uint32_t calc_assigned_dev_id(AssignedDevice *dev) +{ + return (uint32_t)dev->h_segnr << 16 | (uint32_t)dev->h_busnr << 8 | + (uint32_t)dev->h_devfn; +} + +static void assign_failed_examine(AssignedDevice *dev) +{ + char name[PATH_MAX], dir[PATH_MAX], driver[PATH_MAX] = {}, *ns; + uint16_t vendor_id, device_id; + int r; + + sprintf(dir, "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/", + dev->host.seg, dev->host.bus, dev->host.dev, dev->host.func); + + sprintf(name, "%sdriver", dir); + + r = readlink(name, driver, sizeof(driver)); + if ((r <= 0) || r >= sizeof(driver) || !(ns = strrchr(driver, '/'))) { + goto fail; + } + + ns++; + + if (get_real_vendor_id(dir, &vendor_id) || + get_real_device_id(dir, &device_id)) { + goto fail; + } + + fprintf(stderr, "*** The driver '%s' is occupying your device " + "%04x:%02x:%02x.%x.\n", + ns, dev->host.seg, dev->host.bus, dev->host.dev, dev->host.func); + fprintf(stderr, "***\n"); + fprintf(stderr, "*** You can try the following commands to free it:\n"); + fprintf(stderr, "***\n"); + fprintf(stderr, "*** $ echo \"%04x %04x\" > /sys/bus/pci/drivers/pci-stub/" + "new_id\n", vendor_id, device_id); + fprintf(stderr, "*** $ echo \"%04x:%02x:%02x.%x\" > /sys/bus/pci/drivers/" + "%s/unbind\n", + dev->host.seg, dev->host.bus, dev->host.dev, dev->host.func, ns); + fprintf(stderr, "*** $ echo \"%04x:%02x:%02x.%x\" > /sys/bus/pci/drivers/" + "pci-stub/bind\n", + dev->host.seg, dev->host.bus, dev->host.dev, dev->host.func); + fprintf(stderr, "*** $ echo \"%04x %04x\" > /sys/bus/pci/drivers/pci-stub" + "/remove_id\n", vendor_id, device_id); + fprintf(stderr, "***\n"); + + return; + +fail: + fprintf(stderr, "Couldn't find out why.\n"); +} + +static int assign_device(AssignedDevice *dev) +{ + struct kvm_assigned_pci_dev assigned_dev_data; + int r; + + /* Only pass non-zero PCI segment to capable module */ + if (!kvm_check_extension(kvm_state, KVM_CAP_PCI_SEGMENT) && + dev->h_segnr) { + fprintf(stderr, "Can't assign device inside non-zero PCI segment " + "as this KVM module doesn't support it.\n"); + return -ENODEV; + } + + memset(&assigned_dev_data, 0, sizeof(assigned_dev_data)); + assigned_dev_data.assigned_dev_id = calc_assigned_dev_id(dev); + assigned_dev_data.segnr = dev->h_segnr; + assigned_dev_data.busnr = dev->h_busnr; + assigned_dev_data.devfn = dev->h_devfn; + + assigned_dev_data.flags = KVM_DEV_ASSIGN_ENABLE_IOMMU; + if (!kvm_check_extension(kvm_state, KVM_CAP_IOMMU)) { + fprintf(stderr, "No IOMMU found. Unable to assign device \"%s\"\n", + dev->dev.qdev.id); + return -ENODEV; + } + + if (dev->features & ASSIGNED_DEVICE_SHARE_INTX_MASK && + kvm_has_intx_set_mask()) { + assigned_dev_data.flags |= KVM_DEV_ASSIGN_PCI_2_3; + } + + r = kvm_assign_pci_device(kvm_state, &assigned_dev_data); + if (r < 0) { + fprintf(stderr, "Failed to assign device \"%s\" : %s\n", + dev->dev.qdev.id, strerror(-r)); + + switch (r) { + case -EBUSY: + assign_failed_examine(dev); + break; + default: + break; + } + } + return r; +} + +static int assign_irq(AssignedDevice *dev) +{ + struct kvm_assigned_irq assigned_irq_data; + int irq, r = 0; + + /* Interrupt PIN 0 means don't use INTx */ + if (assigned_dev_pci_read_byte(&dev->dev, PCI_INTERRUPT_PIN) == 0) + return 0; + + irq = pci_map_irq(&dev->dev, dev->intpin); + irq = piix_get_irq(irq); + + if (dev->girq == irq) + return r; + + memset(&assigned_irq_data, 0, sizeof(assigned_irq_data)); + assigned_irq_data.assigned_dev_id = calc_assigned_dev_id(dev); + assigned_irq_data.guest_irq = irq; + if (dev->irq_requested_type) { + assigned_irq_data.flags = dev->irq_requested_type; + r = kvm_deassign_irq(kvm_state, &assigned_irq_data); + if (r) { + perror("assign_irq: deassign"); + } + dev->irq_requested_type = 0; + } + +retry: + assigned_irq_data.flags = KVM_DEV_IRQ_GUEST_INTX; + if (dev->features & ASSIGNED_DEVICE_PREFER_MSI_MASK && + dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) + assigned_irq_data.flags |= KVM_DEV_IRQ_HOST_MSI; + else + assigned_irq_data.flags |= KVM_DEV_IRQ_HOST_INTX; + + r = kvm_assign_irq(kvm_state, &assigned_irq_data); + if (r < 0) { + if (r == -EIO && !(dev->features & ASSIGNED_DEVICE_PREFER_MSI_MASK) && + dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) { + /* Retry with host-side MSI. There might be an IRQ conflict and + * either the kernel or the device doesn't support sharing. */ + fprintf(stderr, + "Host-side INTx sharing not supported, " + "using MSI instead.\n" + "Some devices do not to work properly in this mode.\n"); + dev->features |= ASSIGNED_DEVICE_PREFER_MSI_MASK; + goto retry; + } + fprintf(stderr, "Failed to assign irq for \"%s\": %s\n", + dev->dev.qdev.id, strerror(-r)); + fprintf(stderr, "Perhaps you are assigning a device " + "that shares an IRQ with another device?\n"); + return r; + } + + dev->girq = irq; + dev->irq_requested_type = assigned_irq_data.flags; + return r; +} + +static void deassign_device(AssignedDevice *dev) +{ + struct kvm_assigned_pci_dev assigned_dev_data; + int r; + + memset(&assigned_dev_data, 0, sizeof(assigned_dev_data)); + assigned_dev_data.assigned_dev_id = calc_assigned_dev_id(dev); + + r = kvm_deassign_pci_device(kvm_state, &assigned_dev_data); + if (r < 0) + fprintf(stderr, "Failed to deassign device \"%s\" : %s\n", + dev->dev.qdev.id, strerror(-r)); +} + +#if 0 +AssignedDevInfo *get_assigned_device(int pcibus, int slot) +{ + AssignedDevice *assigned_dev = NULL; + AssignedDevInfo *adev = NULL; + + QLIST_FOREACH(adev, &adev_head, next) { + assigned_dev = adev->assigned_dev; + if (pci_bus_num(assigned_dev->dev.bus) == pcibus && + PCI_SLOT(assigned_dev->dev.devfn) == slot) + return adev; + } + + return NULL; +} +#endif + +/* The pci config space got updated. Check if irq numbers have changed + * for our devices + */ +void assigned_dev_update_irqs(void) +{ + AssignedDevice *dev, *next; + Error *err = NULL; + int r; + + dev = QLIST_FIRST(&devs); + while (dev) { + next = QLIST_NEXT(dev, next); + if (dev->irq_requested_type & KVM_DEV_IRQ_HOST_INTX) { + r = assign_irq(dev); + if (r < 0) { + qdev_unplug(&dev->dev.qdev, &err); + assert(!err); + } + } + dev = next; + } +} + +static void assigned_dev_update_msi(PCIDevice *pci_dev) +{ + struct kvm_assigned_irq assigned_irq_data; + AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + uint8_t ctrl_byte = pci_get_byte(pci_dev->config + pci_dev->msi_cap + + PCI_MSI_FLAGS); + int r; + + memset(&assigned_irq_data, 0, sizeof assigned_irq_data); + assigned_irq_data.assigned_dev_id = calc_assigned_dev_id(assigned_dev); + + /* Some guests gratuitously disable MSI even if they're not using it, + * try to catch this by only deassigning irqs if the guest is using + * MSI or intends to start. */ + if ((assigned_dev->irq_requested_type & KVM_DEV_IRQ_GUEST_MSI) || + (ctrl_byte & PCI_MSI_FLAGS_ENABLE)) { + + assigned_irq_data.flags = assigned_dev->irq_requested_type; + free_dev_irq_entries(assigned_dev); + r = kvm_deassign_irq(kvm_state, &assigned_irq_data); + /* -ENXIO means no assigned irq */ + if (r && r != -ENXIO) + perror("assigned_dev_update_msi: deassign irq"); + + assigned_dev->irq_requested_type = 0; + } + + if (ctrl_byte & PCI_MSI_FLAGS_ENABLE) { + uint8_t *pos = pci_dev->config + pci_dev->msi_cap; + + assigned_dev->entry = g_malloc0(sizeof(*(assigned_dev->entry))); + assigned_dev->entry->u.msi.address_lo = + pci_get_long(pos + PCI_MSI_ADDRESS_LO); + assigned_dev->entry->u.msi.address_hi = 0; + assigned_dev->entry->u.msi.data = pci_get_word(pos + PCI_MSI_DATA_32); + assigned_dev->entry->type = KVM_IRQ_ROUTING_MSI; + r = kvm_get_irq_route_gsi(); + if (r < 0) { + perror("assigned_dev_update_msi: kvm_get_irq_route_gsi"); + return; + } + assigned_dev->entry->gsi = r; + + kvm_add_routing_entry(kvm_state, assigned_dev->entry); + if (kvm_irqchip_commit_routes(kvm_state) < 0) { + perror("assigned_dev_update_msi: kvm_commit_irq_routes"); + assigned_dev->cap.state &= ~ASSIGNED_DEVICE_MSI_ENABLED; + return; + } + assigned_dev->irq_entries_nr = 1; + + assigned_irq_data.guest_irq = assigned_dev->entry->gsi; + assigned_irq_data.flags = KVM_DEV_IRQ_HOST_MSI | KVM_DEV_IRQ_GUEST_MSI; + if (kvm_assign_irq(kvm_state, &assigned_irq_data) < 0) { + perror("assigned_dev_enable_msi: assign irq"); + } + + assigned_dev->girq = -1; + assigned_dev->irq_requested_type = assigned_irq_data.flags; + } else { + assign_irq(assigned_dev); + } +} + +static bool msix_masked(MSIXTableEntry *entry) +{ + return (entry->ctrl & cpu_to_le32(0x1)) != 0; +} + +static int assigned_dev_update_msix_mmio(PCIDevice *pci_dev) +{ + AssignedDevice *adev = DO_UPCAST(AssignedDevice, dev, pci_dev); + uint16_t entries_nr = 0; + int i, r = 0; + struct kvm_assigned_msix_nr msix_nr; + struct kvm_assigned_msix_entry msix_entry; + MSIXTableEntry *entry = adev->msix_table; + + /* Get the usable entry number for allocating */ + for (i = 0; i < adev->msix_max; i++, entry++) { + if (msix_masked(entry)) { + continue; + } + entries_nr++; + } + + DEBUG("MSI-X entries: %d\n", entries_nr); + + /* It's valid to enable MSI-X with all entries masked */ + if (!entries_nr) { + return 0; + } + + msix_nr.assigned_dev_id = calc_assigned_dev_id(adev); + msix_nr.entry_nr = entries_nr; + r = kvm_assign_set_msix_nr(kvm_state, &msix_nr); + if (r != 0) { + fprintf(stderr, "fail to set MSI-X entry number for MSIX! %s\n", + strerror(-r)); + return r; + } + + free_dev_irq_entries(adev); + + adev->irq_entries_nr = adev->msix_max; + adev->entry = g_malloc0(adev->msix_max * sizeof(*(adev->entry))); + + msix_entry.assigned_dev_id = msix_nr.assigned_dev_id; + entry = adev->msix_table; + for (i = 0; i < adev->msix_max; i++, entry++) { + if (msix_masked(entry)) { + continue; + } + + r = kvm_get_irq_route_gsi(); + if (r < 0) + return r; + + adev->entry[i].gsi = r; + adev->entry[i].type = KVM_IRQ_ROUTING_MSI; + adev->entry[i].flags = 0; + adev->entry[i].u.msi.address_lo = entry->addr_lo; + adev->entry[i].u.msi.address_hi = entry->addr_hi; + adev->entry[i].u.msi.data = entry->data; + + DEBUG("MSI-X vector %d, gsi %d, addr %08x_%08x, data %08x\n", i, + r, entry->addr_hi, entry->addr_lo, entry->data); + + kvm_add_routing_entry(kvm_state, &adev->entry[i]); + + msix_entry.gsi = adev->entry[i].gsi; + msix_entry.entry = i; + r = kvm_assign_set_msix_entry(kvm_state, &msix_entry); + if (r) { + fprintf(stderr, "fail to set MSI-X entry! %s\n", strerror(-r)); + break; + } + } + + if (r == 0 && kvm_irqchip_commit_routes(kvm_state) < 0) { + perror("assigned_dev_update_msix_mmio: kvm_commit_irq_routes"); + return -EINVAL; + } + + return r; +} + +static void assigned_dev_update_msix(PCIDevice *pci_dev) +{ + struct kvm_assigned_irq assigned_irq_data; + AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + uint16_t ctrl_word = pci_get_word(pci_dev->config + pci_dev->msix_cap + + PCI_MSIX_FLAGS); + int r; + + memset(&assigned_irq_data, 0, sizeof assigned_irq_data); + assigned_irq_data.assigned_dev_id = calc_assigned_dev_id(assigned_dev); + + /* Some guests gratuitously disable MSIX even if they're not using it, + * try to catch this by only deassigning irqs if the guest is using + * MSIX or intends to start. */ + if ((assigned_dev->irq_requested_type & KVM_DEV_IRQ_GUEST_MSIX) || + (ctrl_word & PCI_MSIX_FLAGS_ENABLE)) { + + assigned_irq_data.flags = assigned_dev->irq_requested_type; + free_dev_irq_entries(assigned_dev); + r = kvm_deassign_irq(kvm_state, &assigned_irq_data); + /* -ENXIO means no assigned irq */ + if (r && r != -ENXIO) + perror("assigned_dev_update_msix: deassign irq"); + + assigned_dev->irq_requested_type = 0; + } + + if (ctrl_word & PCI_MSIX_FLAGS_ENABLE) { + assigned_irq_data.flags = KVM_DEV_IRQ_HOST_MSIX | + KVM_DEV_IRQ_GUEST_MSIX; + + if (assigned_dev_update_msix_mmio(pci_dev) < 0) { + perror("assigned_dev_update_msix_mmio"); + return; + } + + if (assigned_dev->irq_entries_nr) { + if (kvm_assign_irq(kvm_state, &assigned_irq_data) < 0) { + perror("assigned_dev_enable_msix: assign irq"); + return; + } + } + assigned_dev->girq = -1; + assigned_dev->irq_requested_type = assigned_irq_data.flags; + } else { + assign_irq(assigned_dev); + } +} + +static uint32_t assigned_dev_pci_read_config(PCIDevice *pci_dev, + uint32_t address, int len) +{ + AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + uint32_t virt_val = pci_default_read_config(pci_dev, address, len); + uint32_t real_val, emulate_mask, full_emulation_mask; + + emulate_mask = 0; + memcpy(&emulate_mask, assigned_dev->emulate_config_read + address, len); + emulate_mask = le32_to_cpu(emulate_mask); + + full_emulation_mask = 0xffffffff >> (32 - len * 8); + + if (emulate_mask != full_emulation_mask) { + real_val = assigned_dev_pci_read(pci_dev, address, len); + return (virt_val & emulate_mask) | (real_val & ~emulate_mask); + } else { + return virt_val; + } +} + +static void assigned_dev_pci_write_config(PCIDevice *pci_dev, uint32_t address, + uint32_t val, int len) +{ + AssignedDevice *assigned_dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + uint16_t old_cmd = pci_get_word(pci_dev->config + PCI_COMMAND); + uint32_t emulate_mask, full_emulation_mask; + int ret; + + pci_default_write_config(pci_dev, address, val, len); + + if (kvm_has_intx_set_mask() && + range_covers_byte(address, len, PCI_COMMAND + 1)) { + bool intx_masked = (pci_get_word(pci_dev->config + PCI_COMMAND) & + PCI_COMMAND_INTX_DISABLE); + + if (intx_masked != !!(old_cmd & PCI_COMMAND_INTX_DISABLE)) { + ret = kvm_device_intx_set_mask(kvm_state, + calc_assigned_dev_id(assigned_dev), + intx_masked); + if (ret) { + perror("assigned_dev_pci_write_config: set intx mask"); + } + } + } + if (assigned_dev->cap.available & ASSIGNED_DEVICE_CAP_MSI) { + if (range_covers_byte(address, len, + pci_dev->msi_cap + PCI_MSI_FLAGS)) { + assigned_dev_update_msi(pci_dev); + } + } + if (assigned_dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) { + if (range_covers_byte(address, len, + pci_dev->msix_cap + PCI_MSIX_FLAGS + 1)) { + assigned_dev_update_msix(pci_dev); + } + } + + emulate_mask = 0; + memcpy(&emulate_mask, assigned_dev->emulate_config_write + address, len); + emulate_mask = le32_to_cpu(emulate_mask); + + full_emulation_mask = 0xffffffff >> (32 - len * 8); + + if (emulate_mask != full_emulation_mask) { + if (emulate_mask) { + val &= ~emulate_mask; + val |= assigned_dev_pci_read(pci_dev, address, len) & emulate_mask; + } + assigned_dev_pci_write(pci_dev, address, val, len); + } +} + +static void assigned_dev_setup_cap_read(AssignedDevice *dev, uint32_t offset, + uint32_t len) +{ + assigned_dev_direct_config_read(dev, offset, len); + assigned_dev_emulate_config_read(dev, offset + PCI_CAP_LIST_NEXT, 1); +} + +static int assigned_device_pci_cap_init(PCIDevice *pci_dev) +{ + AssignedDevice *dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + PCIRegion *pci_region = dev->real_device.regions; + int ret, pos; + + /* Clear initial capabilities pointer and status copied from hw */ + pci_set_byte(pci_dev->config + PCI_CAPABILITY_LIST, 0); + pci_set_word(pci_dev->config + PCI_STATUS, + pci_get_word(pci_dev->config + PCI_STATUS) & + ~PCI_STATUS_CAP_LIST); + + /* Expose MSI capability + * MSI capability is the 1st capability in capability config */ + pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_MSI, 0); + if (pos != 0 && kvm_check_extension(kvm_state, KVM_CAP_ASSIGN_DEV_IRQ)) { + dev->cap.available |= ASSIGNED_DEVICE_CAP_MSI; + /* Only 32-bit/no-mask currently supported */ + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_MSI, pos, 10)) < 0) { + return ret; + } + pci_dev->msi_cap = pos; + + pci_set_word(pci_dev->config + pos + PCI_MSI_FLAGS, + pci_get_word(pci_dev->config + pos + PCI_MSI_FLAGS) & + PCI_MSI_FLAGS_QMASK); + pci_set_long(pci_dev->config + pos + PCI_MSI_ADDRESS_LO, 0); + pci_set_word(pci_dev->config + pos + PCI_MSI_DATA_32, 0); + + /* Set writable fields */ + pci_set_word(pci_dev->wmask + pos + PCI_MSI_FLAGS, + PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE); + pci_set_long(pci_dev->wmask + pos + PCI_MSI_ADDRESS_LO, 0xfffffffc); + pci_set_word(pci_dev->wmask + pos + PCI_MSI_DATA_32, 0xffff); + } + /* Expose MSI-X capability */ + pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_MSIX, 0); + /* Would really like to test kvm_check_extension(, KVM_CAP_DEVICE_MSIX), + * but the kernel doesn't expose it. Instead do a dummy call to + * KVM_ASSIGN_SET_MSIX_NR to see if it exists. */ + if (pos != 0 && kvm_assign_set_msix_nr(kvm_state, NULL) == -EFAULT) { + int bar_nr; + uint32_t msix_table_entry; + + dev->cap.available |= ASSIGNED_DEVICE_CAP_MSIX; + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_MSIX, pos, 12)) < 0) { + return ret; + } + pci_dev->msix_cap = pos; + + pci_set_word(pci_dev->config + pos + PCI_MSIX_FLAGS, + pci_get_word(pci_dev->config + pos + PCI_MSIX_FLAGS) & + PCI_MSIX_FLAGS_QSIZE); + + /* Only enable and function mask bits are writable */ + pci_set_word(pci_dev->wmask + pos + PCI_MSIX_FLAGS, + PCI_MSIX_FLAGS_ENABLE | PCI_MSIX_FLAGS_MASKALL); + + msix_table_entry = pci_get_long(pci_dev->config + pos + PCI_MSIX_TABLE); + bar_nr = msix_table_entry & PCI_MSIX_FLAGS_BIRMASK; + msix_table_entry &= ~PCI_MSIX_FLAGS_BIRMASK; + dev->msix_table_addr = pci_region[bar_nr].base_addr + msix_table_entry; + dev->msix_max = pci_get_word(pci_dev->config + pos + PCI_MSIX_FLAGS); + dev->msix_max &= PCI_MSIX_FLAGS_QSIZE; + dev->msix_max += 1; + } + + /* Minimal PM support, nothing writable, device appears to NAK changes */ + if ((pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_PM, 0))) { + uint16_t pmc; + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_PM, pos, + PCI_PM_SIZEOF)) < 0) { + return ret; + } + + assigned_dev_setup_cap_read(dev, pos, PCI_PM_SIZEOF); + + pmc = pci_get_word(pci_dev->config + pos + PCI_CAP_FLAGS); + pmc &= (PCI_PM_CAP_VER_MASK | PCI_PM_CAP_DSI); + pci_set_word(pci_dev->config + pos + PCI_CAP_FLAGS, pmc); + + /* assign_device will bring the device up to D0, so we don't need + * to worry about doing that ourselves here. */ + pci_set_word(pci_dev->config + pos + PCI_PM_CTRL, + PCI_PM_CTRL_NO_SOFT_RESET); + + pci_set_byte(pci_dev->config + pos + PCI_PM_PPB_EXTENSIONS, 0); + pci_set_byte(pci_dev->config + pos + PCI_PM_DATA_REGISTER, 0); + } + + if ((pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_EXP, 0))) { + uint8_t version, size = 0; + uint16_t type, devctl, lnksta; + uint32_t devcap, lnkcap; + + version = pci_get_byte(pci_dev->config + pos + PCI_EXP_FLAGS); + version &= PCI_EXP_FLAGS_VERS; + if (version == 1) { + size = 0x14; + } else if (version == 2) { + /* + * Check for non-std size, accept reduced size to 0x34, + * which is what bcm5761 implemented, violating the + * PCIe v3.0 spec that regs should exist and be read as 0, + * not optionally provided and shorten the struct size. + */ + size = MIN(0x3c, PCI_CONFIG_SPACE_SIZE - pos); + if (size < 0x34) { + fprintf(stderr, + "%s: Invalid size PCIe cap-id 0x%x \n", + __func__, PCI_CAP_ID_EXP); + return -EINVAL; + } else if (size != 0x3c) { + fprintf(stderr, + "WARNING, %s: PCIe cap-id 0x%x has " + "non-standard size 0x%x; std size should be 0x3c \n", + __func__, PCI_CAP_ID_EXP, size); + } + } else if (version == 0) { + uint16_t vid, did; + vid = pci_get_word(pci_dev->config + PCI_VENDOR_ID); + did = pci_get_word(pci_dev->config + PCI_DEVICE_ID); + if (vid == PCI_VENDOR_ID_INTEL && did == 0x10ed) { + /* + * quirk for Intel 82599 VF with invalid PCIe capability + * version, should really be version 2 (same as PF) + */ + size = 0x3c; + } + } + + if (size == 0) { + fprintf(stderr, + "%s: Unsupported PCI express capability version %d\n", + __func__, version); + return -EINVAL; + } + + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_EXP, + pos, size)) < 0) { + return ret; + } + + assigned_dev_setup_cap_read(dev, pos, size); + + type = pci_get_word(pci_dev->config + pos + PCI_EXP_FLAGS); + type = (type & PCI_EXP_FLAGS_TYPE) >> 4; + if (type != PCI_EXP_TYPE_ENDPOINT && + type != PCI_EXP_TYPE_LEG_END && type != PCI_EXP_TYPE_RC_END) { + fprintf(stderr, + "Device assignment only supports endpoint assignment, " + "device type %d\n", type); + return -EINVAL; + } + + /* capabilities, pass existing read-only copy + * PCI_EXP_FLAGS_IRQ: updated by hardware, should be direct read */ + + /* device capabilities: hide FLR */ + devcap = pci_get_long(pci_dev->config + pos + PCI_EXP_DEVCAP); + devcap &= ~PCI_EXP_DEVCAP_FLR; + pci_set_long(pci_dev->config + pos + PCI_EXP_DEVCAP, devcap); + + /* device control: clear all error reporting enable bits, leaving + * only a few host values. Note, these are + * all writable, but not passed to hw. + */ + devctl = pci_get_word(pci_dev->config + pos + PCI_EXP_DEVCTL); + devctl = (devctl & (PCI_EXP_DEVCTL_READRQ | PCI_EXP_DEVCTL_PAYLOAD)) | + PCI_EXP_DEVCTL_RELAX_EN | PCI_EXP_DEVCTL_NOSNOOP_EN; + pci_set_word(pci_dev->config + pos + PCI_EXP_DEVCTL, devctl); + devctl = PCI_EXP_DEVCTL_BCR_FLR | PCI_EXP_DEVCTL_AUX_PME; + pci_set_word(pci_dev->wmask + pos + PCI_EXP_DEVCTL, ~devctl); + + /* Clear device status */ + pci_set_word(pci_dev->config + pos + PCI_EXP_DEVSTA, 0); + + /* Link capabilities, expose links and latencues, clear reporting */ + lnkcap = pci_get_long(pci_dev->config + pos + PCI_EXP_LNKCAP); + lnkcap &= (PCI_EXP_LNKCAP_SLS | PCI_EXP_LNKCAP_MLW | + PCI_EXP_LNKCAP_ASPMS | PCI_EXP_LNKCAP_L0SEL | + PCI_EXP_LNKCAP_L1EL); + pci_set_long(pci_dev->config + pos + PCI_EXP_LNKCAP, lnkcap); + + /* Link control, pass existing read-only copy. Should be writable? */ + + /* Link status, only expose current speed and width */ + lnksta = pci_get_word(pci_dev->config + pos + PCI_EXP_LNKSTA); + lnksta &= (PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW); + pci_set_word(pci_dev->config + pos + PCI_EXP_LNKSTA, lnksta); + + if (version >= 2) { + /* Slot capabilities, control, status - not needed for endpoints */ + pci_set_long(pci_dev->config + pos + PCI_EXP_SLTCAP, 0); + pci_set_word(pci_dev->config + pos + PCI_EXP_SLTCTL, 0); + pci_set_word(pci_dev->config + pos + PCI_EXP_SLTSTA, 0); + + /* Root control, capabilities, status - not needed for endpoints */ + pci_set_word(pci_dev->config + pos + PCI_EXP_RTCTL, 0); + pci_set_word(pci_dev->config + pos + PCI_EXP_RTCAP, 0); + pci_set_long(pci_dev->config + pos + PCI_EXP_RTSTA, 0); + + /* Device capabilities/control 2, pass existing read-only copy */ + /* Link control 2, pass existing read-only copy */ + } + } + + if ((pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_PCIX, 0))) { + uint16_t cmd; + uint32_t status; + + /* Only expose the minimum, 8 byte capability */ + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_PCIX, pos, 8)) < 0) { + return ret; + } + + assigned_dev_setup_cap_read(dev, pos, 8); + + /* Command register, clear upper bits, including extended modes */ + cmd = pci_get_word(pci_dev->config + pos + PCI_X_CMD); + cmd &= (PCI_X_CMD_DPERR_E | PCI_X_CMD_ERO | PCI_X_CMD_MAX_READ | + PCI_X_CMD_MAX_SPLIT); + pci_set_word(pci_dev->config + pos + PCI_X_CMD, cmd); + + /* Status register, update with emulated PCI bus location, clear + * error bits, leave the rest. */ + status = pci_get_long(pci_dev->config + pos + PCI_X_STATUS); + status &= ~(PCI_X_STATUS_BUS | PCI_X_STATUS_DEVFN); + status |= (pci_bus_num(pci_dev->bus) << 8) | pci_dev->devfn; + status &= ~(PCI_X_STATUS_SPL_DISC | PCI_X_STATUS_UNX_SPL | + PCI_X_STATUS_SPL_ERR); + pci_set_long(pci_dev->config + pos + PCI_X_STATUS, status); + } + + if ((pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_VPD, 0))) { + /* Direct R/W passthrough */ + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_VPD, pos, 8)) < 0) { + return ret; + } + + assigned_dev_setup_cap_read(dev, pos, 8); + + /* direct write for cap content */ + assigned_dev_direct_config_write(dev, pos + 2, 6); + } + + /* Devices can have multiple vendor capabilities, get them all */ + for (pos = 0; (pos = pci_find_cap_offset(pci_dev, PCI_CAP_ID_VNDR, pos)); + pos += PCI_CAP_LIST_NEXT) { + uint8_t len = pci_get_byte(pci_dev->config + pos + PCI_CAP_FLAGS); + /* Direct R/W passthrough */ + if ((ret = pci_add_capability(pci_dev, PCI_CAP_ID_VNDR, + pos, len)) < 0) { + return ret; + } + + assigned_dev_setup_cap_read(dev, pos, len); + + /* direct write for cap content */ + assigned_dev_direct_config_write(dev, pos + 2, len - 2); + } + + /* If real and virtual capability list status bits differ, virtualize the + * access. */ + if ((pci_get_word(pci_dev->config + PCI_STATUS) & PCI_STATUS_CAP_LIST) != + (assigned_dev_pci_read_byte(pci_dev, PCI_STATUS) & + PCI_STATUS_CAP_LIST)) { + dev->emulate_config_read[PCI_STATUS] |= PCI_STATUS_CAP_LIST; + } + + return 0; +} + +static uint64_t msix_mmio_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + AssignedDevice *adev = opaque; + uint64_t val; + + memcpy(&val, (void *)((uint8_t *)adev->msix_table + addr), size); + + return val; +} + +static void msix_mmio_write(void *opaque, target_phys_addr_t addr, + uint64_t val, unsigned size) +{ + AssignedDevice *adev = opaque; + PCIDevice *pdev = &adev->dev; + uint16_t ctrl; + MSIXTableEntry orig; + int i = addr >> 4; + + if (i >= adev->msix_max) { + return; /* Drop write */ + } + + ctrl = pci_get_word(pdev->config + pdev->msix_cap + PCI_MSIX_FLAGS); + + DEBUG("write to MSI-X table offset 0x%lx, val 0x%lx\n", addr, val); + + if (ctrl & PCI_MSIX_FLAGS_ENABLE) { + orig = adev->msix_table[i]; + } + + memcpy((void *)((uint8_t *)adev->msix_table + addr), &val, size); + + if (ctrl & PCI_MSIX_FLAGS_ENABLE) { + MSIXTableEntry *entry = &adev->msix_table[i]; + + if (!msix_masked(&orig) && msix_masked(entry)) { + /* + * Vector masked, disable it + * + * XXX It's not clear if we can or should actually attempt + * to mask or disable the interrupt. KVM doesn't have + * support for pending bits and kvm_assign_set_msix_entry + * doesn't modify the device hardware mask. Interrupts + * while masked are simply not injected to the guest, so + * are lost. Can we get away with always injecting an + * interrupt on unmask? + */ + } else if (msix_masked(&orig) && !msix_masked(entry)) { + /* Vector unmasked */ + if (i >= adev->irq_entries_nr || !adev->entry[i].type) { + /* Previously unassigned vector, start from scratch */ + assigned_dev_update_msix(pdev); + return; + } else { + /* Update an existing, previously masked vector */ + struct kvm_irq_routing_entry orig = adev->entry[i]; + int ret; + + adev->entry[i].u.msi.address_lo = entry->addr_lo; + adev->entry[i].u.msi.address_hi = entry->addr_hi; + adev->entry[i].u.msi.data = entry->data; + + ret = kvm_update_routing_entry(&orig, &adev->entry[i]); + if (ret) { + fprintf(stderr, + "Error updating irq routing entry (%d)\n", ret); + return; + } + + ret = kvm_irqchip_commit_routes(kvm_state); + if (ret) { + fprintf(stderr, + "Error committing irq routes (%d)\n", ret); + return; + } + } + } + } +} + +static const MemoryRegionOps msix_mmio_ops = { + .read = msix_mmio_read, + .write = msix_mmio_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 8, + }, +}; + +static void msix_reset(AssignedDevice *dev) +{ + MSIXTableEntry *entry; + int i; + + if (!dev->msix_table) { + return; + } + + memset(dev->msix_table, 0, MSIX_PAGE_SIZE); + + for (i = 0, entry = dev->msix_table; i < dev->msix_max; i++, entry++) { + entry->ctrl = cpu_to_le32(0x1); /* Masked */ + } +} + +static int assigned_dev_register_msix_mmio(AssignedDevice *dev) +{ + dev->msix_table = mmap(NULL, MSIX_PAGE_SIZE, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); + if (dev->msix_table == MAP_FAILED) { + fprintf(stderr, "fail allocate msix_table! %s\n", strerror(errno)); + return -EFAULT; + } + + msix_reset(dev); + + memory_region_init_io(&dev->mmio, &msix_mmio_ops, dev, + "assigned-dev-msix", MSIX_PAGE_SIZE); + return 0; +} + +static void assigned_dev_unregister_msix_mmio(AssignedDevice *dev) +{ + if (!dev->msix_table) { + return; + } + + memory_region_destroy(&dev->mmio); + + if (munmap(dev->msix_table, MSIX_PAGE_SIZE) == -1) { + fprintf(stderr, "error unmapping msix_table! %s\n", + strerror(errno)); + } + dev->msix_table = NULL; +} + +static const VMStateDescription vmstate_assigned_device = { + .name = "pci-assign", + .unmigratable = 1, +}; + +static void reset_assigned_device(DeviceState *dev) +{ + PCIDevice *pci_dev = DO_UPCAST(PCIDevice, qdev, dev); + AssignedDevice *adev = DO_UPCAST(AssignedDevice, dev, pci_dev); + char reset_file[64]; + const char reset[] = "1"; + int fd, ret; + + /* + * If a guest is reset without being shutdown, MSI/MSI-X can still + * be running. We want to return the device to a known state on + * reset, so disable those here. We especially do not want MSI-X + * enabled since it lives in MMIO space, which is about to get + * disabled. + */ + if (adev->irq_requested_type & KVM_DEV_IRQ_GUEST_MSIX) { + uint16_t ctrl = pci_get_word(pci_dev->config + + pci_dev->msix_cap + PCI_MSIX_FLAGS); + + pci_set_word(pci_dev->config + pci_dev->msix_cap + PCI_MSIX_FLAGS, + ctrl & ~PCI_MSIX_FLAGS_ENABLE); + assigned_dev_update_msix(pci_dev); + } else if (adev->irq_requested_type & KVM_DEV_IRQ_GUEST_MSI) { + uint8_t ctrl = pci_get_byte(pci_dev->config + + pci_dev->msi_cap + PCI_MSI_FLAGS); + + pci_set_byte(pci_dev->config + pci_dev->msi_cap + PCI_MSI_FLAGS, + ctrl & ~PCI_MSI_FLAGS_ENABLE); + assigned_dev_update_msi(pci_dev); + } + + snprintf(reset_file, sizeof(reset_file), + "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/reset", + adev->host.seg, adev->host.bus, adev->host.dev, adev->host.func); + + /* + * Issue a device reset via pci-sysfs. Note that we use write(2) here + * and ignore the return value because some kernels have a bug that + * returns 0 rather than bytes written on success, sending us into an + * infinite retry loop using other write mechanisms. + */ + fd = open(reset_file, O_WRONLY); + if (fd != -1) { + ret = write(fd, reset, strlen(reset)); + (void)ret; + close(fd); + } + + /* + * When a 0 is written to the bus master register, the device is logically + * disconnected from the PCI bus. This avoids further DMA transfers. + */ + assigned_dev_pci_write_config(pci_dev, PCI_COMMAND, 0, 1); +} + +static int assigned_initfn(struct PCIDevice *pci_dev) +{ + AssignedDevice *dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + uint8_t e_intx; + int r; + + if (!kvm_enabled()) { + error_report("pci-assign: error: requires KVM support"); + return -1; + } + + if (!dev->host.seg && !dev->host.bus && !dev->host.dev && !dev->host.func) { + error_report("pci-assign: error: no host device specified"); + return -1; + } + + /* + * Set up basic config space access control. Will be further refined during + * device initialization. + */ + assigned_dev_emulate_config_read(dev, 0, PCI_CONFIG_SPACE_SIZE); + assigned_dev_direct_config_read(dev, PCI_STATUS, 2); + assigned_dev_direct_config_read(dev, PCI_REVISION_ID, 1); + assigned_dev_direct_config_read(dev, PCI_CLASS_PROG, 3); + assigned_dev_direct_config_read(dev, PCI_CACHE_LINE_SIZE, 1); + assigned_dev_direct_config_read(dev, PCI_LATENCY_TIMER, 1); + assigned_dev_direct_config_read(dev, PCI_BIST, 1); + assigned_dev_direct_config_read(dev, PCI_CARDBUS_CIS, 4); + assigned_dev_direct_config_read(dev, PCI_SUBSYSTEM_VENDOR_ID, 2); + assigned_dev_direct_config_read(dev, PCI_SUBSYSTEM_ID, 2); + assigned_dev_direct_config_read(dev, PCI_CAPABILITY_LIST + 1, 7); + assigned_dev_direct_config_read(dev, PCI_MIN_GNT, 1); + assigned_dev_direct_config_read(dev, PCI_MAX_LAT, 1); + memcpy(dev->emulate_config_write, dev->emulate_config_read, + sizeof(dev->emulate_config_read)); + + if (get_real_device(dev, dev->host.seg, dev->host.bus, + dev->host.dev, dev->host.func)) { + error_report("pci-assign: Error: Couldn't get real device (%s)!", + dev->dev.qdev.id); + goto out; + } + + if (assigned_device_pci_cap_init(pci_dev) < 0) { + goto out; + } + + /* intercept MSI-X entry page in the MMIO */ + if (dev->cap.available & ASSIGNED_DEVICE_CAP_MSIX) { + if (assigned_dev_register_msix_mmio(dev)) { + goto out; + } + } + + /* handle real device's MMIO/PIO BARs */ + if (assigned_dev_register_regions(dev->real_device.regions, + dev->real_device.region_number, + dev)) + goto out; + + /* handle interrupt routing */ + e_intx = dev->dev.config[0x3d] - 1; + dev->intpin = e_intx; + dev->run = 0; + dev->girq = -1; + dev->h_segnr = dev->host.seg; + dev->h_busnr = dev->host.bus; + dev->h_devfn = PCI_DEVFN(dev->host.dev, dev->host.func); + + /* assign device to guest */ + r = assign_device(dev); + if (r < 0) + goto out; + + /* assign irq for the device */ + r = assign_irq(dev); + if (r < 0) + goto assigned_out; + + assigned_dev_load_option_rom(dev); + QLIST_INSERT_HEAD(&devs, dev, next); + + add_boot_device_path(dev->bootindex, &pci_dev->qdev, NULL); + + return 0; + +assigned_out: + deassign_device(dev); +out: + free_assigned_device(dev); + return -1; +} + +static int assigned_exitfn(struct PCIDevice *pci_dev) +{ + AssignedDevice *dev = DO_UPCAST(AssignedDevice, dev, pci_dev); + + QLIST_REMOVE(dev, next); + deassign_device(dev); + free_assigned_device(dev); + return 0; +} + +static int parse_hostaddr(DeviceState *dev, Property *prop, const char *str) +{ + PCIHostDevice *ptr = qdev_get_prop_ptr(dev, prop); + int rc; + + rc = pci_parse_host_devaddr(str, &ptr->seg, &ptr->bus, &ptr->dev, &ptr->func); + if (rc != 0) + return -1; + return 0; +} + +static int print_hostaddr(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + PCIHostDevice *ptr = qdev_get_prop_ptr(dev, prop); + + return snprintf(dest, len, "%02x:%02x.%x", ptr->bus, ptr->dev, ptr->func); +} + +PropertyInfo qdev_prop_hostaddr = { + .name = "pci-hostaddr", + .parse = parse_hostaddr, + .print = print_hostaddr, +}; + +static Property da_properties[] = +{ + DEFINE_PROP("host", AssignedDevice, host, qdev_prop_hostaddr, PCIHostDevice), + DEFINE_PROP_BIT("prefer_msi", AssignedDevice, features, + ASSIGNED_DEVICE_PREFER_MSI_BIT, false), + DEFINE_PROP_BIT("share_intx", AssignedDevice, features, + ASSIGNED_DEVICE_SHARE_INTX_BIT, true), + DEFINE_PROP_INT32("bootindex", AssignedDevice, bootindex, -1), + DEFINE_PROP_STRING("configfd", AssignedDevice, configfd_name), + DEFINE_PROP_END_OF_LIST(), +}; + +static void assign_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + k->init = assigned_initfn; + k->exit = assigned_exitfn; + k->config_read = assigned_dev_pci_read_config; + k->config_write = assigned_dev_pci_write_config; + dc->props = da_properties; + dc->vmsd = &vmstate_assigned_device; + dc->reset = reset_assigned_device; +} + +static TypeInfo assign_info = { + .name = "pci-assign", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(AssignedDevice), + .class_init = assign_class_init, +}; + +static void assign_register_types(void) +{ + type_register_static(&assign_info); +} + +type_init(assign_register_types) + +/* + * Scan the assigned devices for the devices that have an option ROM, and then + * load the corresponding ROM data to RAM. If an error occurs while loading an + * option ROM, we just ignore that option ROM and continue with the next one. + */ +static void assigned_dev_load_option_rom(AssignedDevice *dev) +{ + char name[32], rom_file[64]; + FILE *fp; + uint8_t val; + struct stat st; + void *ptr; + + /* If loading ROM from file, pci handles it */ + if (dev->dev.romfile || !dev->dev.rom_bar) + return; + + snprintf(rom_file, sizeof(rom_file), + "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/rom", + dev->host.seg, dev->host.bus, dev->host.dev, dev->host.func); + + if (stat(rom_file, &st)) { + return; + } + + if (access(rom_file, F_OK)) { + fprintf(stderr, "pci-assign: Insufficient privileges for %s\n", + rom_file); + return; + } + + /* Write "1" to the ROM file to enable it */ + fp = fopen(rom_file, "r+"); + if (fp == NULL) { + return; + } + val = 1; + if (fwrite(&val, 1, 1, fp) != 1) { + goto close_rom; + } + fseek(fp, 0, SEEK_SET); + + snprintf(name, sizeof(name), "%s.rom", + object_get_typename(OBJECT(dev))); + memory_region_init_ram(&dev->dev.rom, name, st.st_size); + vmstate_register_ram(&dev->dev.rom, &dev->dev.qdev); + ptr = memory_region_get_ram_ptr(&dev->dev.rom); + memset(ptr, 0xff, st.st_size); + + if (!fread(ptr, 1, st.st_size, fp)) { + fprintf(stderr, "pci-assign: Cannot read from host %s\n" + "\tDevice option ROM contents are probably invalid " + "(check dmesg).\n\tSkip option ROM probe with rombar=0, " + "or load from file with romfile=\n", rom_file); + memory_region_destroy(&dev->dev.rom); + goto close_rom; + } + + pci_register_bar(&dev->dev, PCI_ROM_SLOT, 0, &dev->dev.rom); + dev->dev.has_rom = true; +close_rom: + /* Write "0" to disable ROM */ + fseek(fp, 0, SEEK_SET); + val = 0; + if (!fwrite(&val, 1, 1, fp)) { + DEBUG("%s\n", "Failed to disable pci-sysfs rom file"); + } + fclose(fp); +} diff --git a/hw/device-assignment.h b/hw/device-assignment.h new file mode 100644 index 000000000..3fcb80400 --- /dev/null +++ b/hw/device-assignment.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007, Neocleus Corporation. + * Copyright (c) 2007, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Data structures for storing PCI state + * + * Adapted to kvm by Qumranet + * + * Copyright (c) 2007, Neocleus, Alex Novik (alex@neocleus.com) + * Copyright (c) 2007, Neocleus, Guy Zana (guy@neocleus.com) + * Copyright (C) 2008, Qumranet, Amit Shah (amit.shah@qumranet.com) + * Copyright (C) 2008, Red Hat, Amit Shah (amit.shah@redhat.com) + */ + +#ifndef __DEVICE_ASSIGNMENT_H__ +#define __DEVICE_ASSIGNMENT_H__ + +void assigned_dev_update_irqs(void); + +#endif /* __DEVICE_ASSIGNMENT_H__ */ diff --git a/hw/i8254_common.c b/hw/i8254_common.c index a03d7cd45..b01ad70d5 100644 --- a/hw/i8254_common.c +++ b/hw/i8254_common.c @@ -275,7 +275,7 @@ static const VMStateDescription vmstate_pit_common = { .pre_save = pit_dispatch_pre_save, .post_load = pit_dispatch_post_load, .fields = (VMStateField[]) { - VMSTATE_UINT32_V(channels[0].irq_disabled, PITCommonState, 3), + VMSTATE_UINT32(channels[0].irq_disabled, PITCommonState), /* qemu-kvm's v2 had 'flags' here */ VMSTATE_STRUCT_ARRAY(channels, PITCommonState, 3, 2, vmstate_pit_channel, PITChannelState), VMSTATE_INT64(channels[0].next_transition_time, @@ -20,6 +20,7 @@ #include "msi.h" #include "range.h" +#include "kvm.h" /* Eventually those constants should go to Linux pci_regs.h */ #define PCI_MSI_PENDING_32 0x10 @@ -112,6 +113,94 @@ bool msi_enabled(const PCIDevice *dev) PCI_MSI_FLAGS_ENABLE); } +static void kvm_msi_message_from_vector(PCIDevice *dev, unsigned vector, + KVMMsiMessage *kmm) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + bool msi64bit = flags & PCI_MSI_FLAGS_64BIT; + unsigned int nr_vectors = msi_nr_vectors(flags); + + kmm->addr_lo = pci_get_long(dev->config + msi_address_lo_off(dev)); + if (msi64bit) { + kmm->addr_hi = pci_get_long(dev->config + msi_address_hi_off(dev)); + } else { + kmm->addr_hi = 0; + } + + kmm->data = pci_get_word(dev->config + msi_data_off(dev, msi64bit)); + if (nr_vectors > 1) { + kmm->data &= ~(nr_vectors - 1); + kmm->data |= vector; + } +} + +static void kvm_msi_update(PCIDevice *dev) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + unsigned int max_vectors = 1 << + ((flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1)); + unsigned int nr_vectors = msi_nr_vectors(flags); + KVMMsiMessage new_entry, *entry; + bool changed = false; + unsigned int vector; + int r; + + for (vector = 0; vector < max_vectors; vector++) { + entry = dev->msi_irq_entries + vector; + + if (vector >= nr_vectors) { + if (vector < dev->msi_entries_nr) { + kvm_msi_message_del(entry); + changed = true; + } + } else if (vector >= dev->msi_entries_nr) { + kvm_msi_message_from_vector(dev, vector, entry); + r = kvm_msi_message_add(entry); + if (r) { + fprintf(stderr, "%s: kvm_msi_add failed: %s\n", __func__, + strerror(-r)); + exit(1); + } + changed = true; + } else { + kvm_msi_message_from_vector(dev, vector, &new_entry); + r = kvm_msi_message_update(entry, &new_entry); + if (r < 0) { + fprintf(stderr, "%s: kvm_update_msi failed: %s\n", + __func__, strerror(-r)); + exit(1); + } + if (r > 0) { + *entry = new_entry; + changed = true; + } + } + } + dev->msi_entries_nr = nr_vectors; + if (changed) { + r = kvm_irqchip_commit_routes(kvm_state); + if (r) { + fprintf(stderr, "%s: kvm_commit_irq_routes failed: %s\n", __func__, + strerror(-r)); + exit(1); + } + } +} + +/* KVM specific MSI helpers */ +static void kvm_msi_free(PCIDevice *dev) +{ + unsigned int vector; + + for (vector = 0; vector < dev->msi_entries_nr; ++vector) { + kvm_msi_message_del(&dev->msi_irq_entries[vector]); + } + if (dev->msi_entries_nr > 0) { + kvm_irqchip_commit_routes(kvm_state); + } + dev->msi_entries_nr = 0; +} + int msi_init(struct PCIDevice *dev, uint8_t offset, unsigned int nr_vectors, bool msi64bit, bool msi_per_vector_mask) { @@ -167,6 +256,12 @@ int msi_init(struct PCIDevice *dev, uint8_t offset, pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit), 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors)); } + + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + dev->msi_irq_entries = g_malloc(nr_vectors * + sizeof(*dev->msix_irq_entries)); + } + return config_offset; } @@ -180,6 +275,12 @@ void msi_uninit(struct PCIDevice *dev) } flags = pci_get_word(dev->config + msi_flags_off(dev)); cap_size = msi_cap_sizeof(flags); + + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_msi_free(dev); + g_free(dev->msi_irq_entries); + } + pci_del_capability(dev, PCI_CAP_ID_MSI, cap_size); dev->cap_present &= ~QEMU_PCI_CAP_MSI; @@ -191,6 +292,10 @@ void msi_reset(PCIDevice *dev) uint16_t flags; bool msi64bit; + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_msi_free(dev); + } + flags = pci_get_word(dev->config + msi_flags_off(dev)); flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE); msi64bit = flags & PCI_MSI_FLAGS_64BIT; @@ -240,6 +345,11 @@ void msi_notify(PCIDevice *dev, unsigned int vector) return; } + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_irqchip_set_irq(kvm_state, dev->msi_irq_entries[vector].gsi, 1); + return; + } + if (msi64bit) { address = pci_get_quad(dev->config + msi_address_lo_off(dev)); } else { @@ -328,6 +438,10 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len) pci_set_word(dev->config + msi_flags_off(dev), flags); } + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_msi_update(dev); + } + if (!msi_per_vector_mask) { /* if per vector masking isn't supported, there is no pending interrupt. */ @@ -358,3 +472,16 @@ unsigned int msi_nr_vectors_allocated(const PCIDevice *dev) uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); return msi_nr_vectors(flags); } + +void msi_post_load(PCIDevice *dev) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + + if (kvm_enabled() && dev->msi_irq_entries) { + kvm_msi_free(dev); + + if (flags & PCI_MSI_FLAGS_ENABLE) { + kvm_msi_update(dev); + } + } +} @@ -34,6 +34,7 @@ void msi_reset(PCIDevice *dev); void msi_notify(PCIDevice *dev, unsigned int vector); void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len); unsigned int msi_nr_vectors_allocated(const PCIDevice *dev); +void msi_post_load(PCIDevice *dev); static inline bool msi_present(const PCIDevice *dev) { @@ -19,6 +19,7 @@ #include "msix.h" #include "pci.h" #include "range.h" +#include "kvm.h" #define MSIX_CAP_LENGTH 12 @@ -36,6 +37,93 @@ #define MSIX_MAX_ENTRIES 32 +/* KVM specific MSIX helpers */ +static void kvm_msix_free(PCIDevice *dev) +{ + int vector, changed = 0; + + for (vector = 0; vector < dev->msix_entries_nr; ++vector) { + if (dev->msix_entry_used[vector]) { + kvm_msi_message_del(&dev->msix_irq_entries[vector]); + changed = 1; + } + } + if (changed) { + kvm_irqchip_commit_routes(kvm_state); + } +} + +static void kvm_msix_message_from_vector(PCIDevice *dev, unsigned vector, + KVMMsiMessage *kmm) +{ + uint8_t *table_entry = dev->msix_table_page + vector * PCI_MSIX_ENTRY_SIZE; + + kmm->addr_lo = pci_get_long(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR); + kmm->addr_hi = pci_get_long(table_entry + PCI_MSIX_ENTRY_UPPER_ADDR); + kmm->data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA); +} + +static void kvm_msix_update(PCIDevice *dev, int vector, + int was_masked, int is_masked) +{ + KVMMsiMessage new_entry, *entry; + int mask_cleared = was_masked && !is_masked; + int r; + + /* It is only legal to change an entry when it is masked. Therefore, it is + * enough to update the routing in kernel when mask is being cleared. */ + if (!mask_cleared) { + return; + } + if (!dev->msix_entry_used[vector]) { + return; + } + + entry = dev->msix_irq_entries + vector; + kvm_msix_message_from_vector(dev, vector, &new_entry); + r = kvm_msi_message_update(entry, &new_entry); + if (r < 0) { + fprintf(stderr, "%s: kvm_update_msix failed: %s\n", __func__, + strerror(-r)); + exit(1); + } + if (r > 0) { + *entry = new_entry; + r = kvm_irqchip_commit_routes(kvm_state); + if (r) { + fprintf(stderr, "%s: kvm_commit_irq_routes failed: %s\n", __func__, + strerror(-r)); + exit(1); + } + } +} + +static int kvm_msix_vector_add(PCIDevice *dev, unsigned vector) +{ + KVMMsiMessage *kmm = dev->msix_irq_entries + vector; + int r; + + kvm_msix_message_from_vector(dev, vector, kmm); + r = kvm_msi_message_add(kmm); + if (r < 0) { + fprintf(stderr, "%s: kvm_add_msix failed: %s\n", __func__, strerror(-r)); + return r; + } + + r = kvm_irqchip_commit_routes(kvm_state); + if (r < 0) { + fprintf(stderr, "%s: kvm_commit_irq_routes failed: %s\n", __func__, strerror(-r)); + return r; + } + return 0; +} + +static void kvm_msix_vector_del(PCIDevice *dev, unsigned vector) +{ + kvm_msi_message_del(&dev->msix_irq_entries[vector]); + kvm_irqchip_commit_routes(kvm_state); +} + /* Add MSI-X capability to the config space for the device. */ /* Given a bar and its size, add MSI-X table on top of it * and fill MSI-X capability in the config space. @@ -137,6 +225,12 @@ static void msix_handle_mask_update(PCIDevice *dev, int vector, bool was_masked) return; } + if (dev->msix_mask_notifier) { + int ret; + ret = dev->msix_mask_notifier(dev, vector, is_masked); + assert(ret >= 0); + } + if (!is_masked && msix_is_pending(dev, vector)) { msix_clr_pending(dev, vector); msix_notify(dev, vector); @@ -195,6 +289,9 @@ static void msix_mmio_write(void *opaque, target_phys_addr_t addr, was_masked = msix_is_masked(dev, vector); pci_set_long(dev->msix_table_page + offset, val); + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_msix_update(dev, vector, was_masked, msix_is_masked(dev, vector)); + } msix_handle_mask_update(dev, vector, was_masked); } @@ -221,11 +318,18 @@ static void msix_mmio_setup(PCIDevice *d, MemoryRegion *bar) static void msix_mask_all(struct PCIDevice *dev, unsigned nentries) { - int vector; + int vector, r; for (vector = 0; vector < nentries; ++vector) { unsigned offset = vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL; + int was_masked = msix_is_masked(dev, vector); dev->msix_table_page[offset] |= PCI_MSIX_ENTRY_CTRL_MASKBIT; + if (was_masked != msix_is_masked(dev, vector) && + dev->msix_mask_notifier) { + r = dev->msix_mask_notifier(dev, vector, + msix_is_masked(dev, vector)); + assert(r >= 0); + } } } @@ -244,6 +348,7 @@ int msix_init(struct PCIDevice *dev, unsigned short nentries, if (nentries > MSIX_MAX_ENTRIES) return -EINVAL; + dev->msix_mask_notifier = NULL; dev->msix_entry_used = g_malloc0(MSIX_MAX_ENTRIES * sizeof *dev->msix_entry_used); @@ -258,6 +363,11 @@ int msix_init(struct PCIDevice *dev, unsigned short nentries, if (ret) goto err_config; + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + dev->msix_irq_entries = g_malloc(nentries * + sizeof *dev->msix_irq_entries); + } + dev->cap_present |= QEMU_PCI_CAP_MSIX; msix_mmio_setup(dev, bar); return 0; @@ -276,6 +386,10 @@ static void msix_free_irq_entries(PCIDevice *dev) { int vector; + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_msix_free(dev); + } + for (vector = 0; vector < dev->msix_entries_nr; ++vector) { dev->msix_entry_used[vector] = 0; msix_clr_pending(dev, vector); @@ -306,6 +420,8 @@ int msix_uninit(PCIDevice *dev, MemoryRegion *bar) dev->msix_table_page = NULL; g_free(dev->msix_entry_used); dev->msix_entry_used = NULL; + g_free(dev->msix_irq_entries); + dev->msix_irq_entries = NULL; dev->cap_present &= ~QEMU_PCI_CAP_MSIX; return 0; } @@ -317,7 +433,6 @@ void msix_save(PCIDevice *dev, QEMUFile *f) if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) { return; } - qemu_put_buffer(f, dev->msix_table_page, n * PCI_MSIX_ENTRY_SIZE); qemu_put_buffer(f, dev->msix_table_page + MSIX_PAGE_PENDING, (n + 7) / 8); } @@ -372,6 +487,11 @@ void msix_notify(PCIDevice *dev, unsigned vector) return; } + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_irqchip_set_irq(kvm_state, dev->msix_irq_entries[vector].gsi, 1); + return; + } + address = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR); data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA); stl_le_phys(address, data); @@ -399,9 +519,17 @@ void msix_reset(PCIDevice *dev) /* Mark vector as used. */ int msix_vector_use(PCIDevice *dev, unsigned vector) { + int ret; if (vector >= dev->msix_entries_nr) return -EINVAL; - dev->msix_entry_used[vector]++; + if (kvm_enabled() && kvm_irqchip_in_kernel() && + !dev->msix_entry_used[vector]) { + ret = kvm_msix_vector_add(dev, vector); + if (ret) { + return ret; + } + } + ++dev->msix_entry_used[vector]; return 0; } @@ -414,6 +542,9 @@ void msix_vector_unuse(PCIDevice *dev, unsigned vector) if (--dev->msix_entry_used[vector]) { return; } + if (kvm_enabled() && kvm_irqchip_in_kernel()) { + kvm_msix_vector_del(dev, vector); + } msix_clr_pending(dev, vector); } @@ -423,3 +554,66 @@ void msix_unuse_all_vectors(PCIDevice *dev) return; msix_free_irq_entries(dev); } + +/* Invoke the notifier if vector entry is used and unmasked. */ +static int msix_notify_if_unmasked(PCIDevice *dev, unsigned vector, int masked) +{ + assert(dev->msix_mask_notifier); + if (!dev->msix_entry_used[vector] || msix_is_masked(dev, vector)) { + return 0; + } + return dev->msix_mask_notifier(dev, vector, masked); +} + +static int msix_set_mask_notifier_for_vector(PCIDevice *dev, unsigned vector) +{ + /* Notifier has been set. Invoke it on unmasked vectors. */ + return msix_notify_if_unmasked(dev, vector, 0); +} + +static int msix_unset_mask_notifier_for_vector(PCIDevice *dev, unsigned vector) +{ + /* Notifier will be unset. Invoke it to mask unmasked entries. */ + return msix_notify_if_unmasked(dev, vector, 1); +} + +int msix_set_mask_notifier(PCIDevice *dev, msix_mask_notifier_func f) +{ + int r, n; + assert(!dev->msix_mask_notifier); + dev->msix_mask_notifier = f; + for (n = 0; n < dev->msix_entries_nr; ++n) { + r = msix_set_mask_notifier_for_vector(dev, n); + if (r < 0) { + goto undo; + } + } + return 0; + +undo: + while (--n >= 0) { + msix_unset_mask_notifier_for_vector(dev, n); + } + dev->msix_mask_notifier = NULL; + return r; +} + +int msix_unset_mask_notifier(PCIDevice *dev) +{ + int r, n; + assert(dev->msix_mask_notifier); + for (n = 0; n < dev->msix_entries_nr; ++n) { + r = msix_unset_mask_notifier_for_vector(dev, n); + if (r < 0) { + goto undo; + } + } + dev->msix_mask_notifier = NULL; + return 0; + +undo: + while (--n >= 0) { + msix_set_mask_notifier_for_vector(dev, n); + } + return r; +} @@ -29,4 +29,6 @@ void msix_notify(PCIDevice *dev, unsigned vector); void msix_reset(PCIDevice *dev); +int msix_set_mask_notifier(PCIDevice *dev, msix_mask_notifier_func); +int msix_unset_mask_notifier(PCIDevice *dev); #endif @@ -917,10 +917,14 @@ static DeviceState *apic_init(void *env, uint8_t apic_id) apic_mapped = 1; } +#ifdef UPSTREAM_KVM /* KVM does not support MSI yet. */ if (!kvm_irqchip_in_kernel()) { msi_supported = true; } +#else + msi_supported = true; +#endif if (xen_msi_support()) { msi_supported = true; @@ -946,10 +950,18 @@ static void pc_cpu_reset(void *opaque) env->halted = !cpu_is_bsp(env); } -static CPUX86State *pc_new_cpu(const char *cpu_model) +CPUX86State *pc_new_cpu(const char *cpu_model) { CPUX86State *env; + if (cpu_model == NULL) { +#ifdef TARGET_X86_64 + cpu_model = "qemu64"; +#else + cpu_model = "qemu32"; +#endif + } + env = cpu_init(cpu_model); if (!env) { fprintf(stderr, "Unable to find x86 CPU definition\n"); @@ -968,14 +980,6 @@ void pc_cpus_init(const char *cpu_model) int i; /* init CPUs */ - if (cpu_model == NULL) { -#ifdef TARGET_X86_64 - cpu_model = "qemu64"; -#else - cpu_model = "qemu32"; -#endif - } - for(i = 0; i < smp_cpus; i++) { pc_new_cpu(cpu_model); } @@ -149,6 +149,9 @@ void piix4_smbus_register_device(SMBusDevice *dev, uint8_t addr); extern int no_hpet; /* piix_pci.c */ +/* config space register for IRQ routing */ +#define PIIX_CONFIG_IRQ_ROUTE 0x60 + struct PCII440FXState; typedef struct PCII440FXState PCII440FXState; @@ -168,6 +171,10 @@ PCIBus *i440fx_init(PCII440FXState **pi440fx_state, int *piix_devfn, extern PCIDevice *piix4_dev; int piix4_init(PCIBus *bus, ISABus **isa_bus, int devfn); +int piix_get_irq(int pin); + +int ipf_map_irq(PCIDevice *pci_dev, int irq_num); + /* vga.c */ enum vga_retrace_method { VGA_RETRACE_DUMB, diff --git a/hw/pc_piix.c b/hw/pc_piix.c index a7aad4b02..4e8a280a4 100644 --- a/hw/pc_piix.c +++ b/hw/pc_piix.c @@ -52,6 +52,8 @@ static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 }; static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 }; static const int ide_irq[MAX_IDE_BUS] = { 14, 15 }; +const char *global_cpu_model; /* cpu hotadd */ + static void kvm_piix3_setup_irq_routing(bool pci_enabled) { #ifdef CONFIG_KVM @@ -151,6 +153,8 @@ static void pc_init1(MemoryRegion *system_memory, MemoryRegion *pci_memory; MemoryRegion *rom_memory; + global_cpu_model = cpu_model; + pc_cpus_init(cpu_model); if (kvmclock_enabled) { @@ -241,7 +245,7 @@ static void pc_init1(MemoryRegion *system_memory, if (!pci_enabled || (nd->model && strcmp(nd->model, "ne2k_isa") == 0)) pc_init_ne2k_isa(isa_bus, nd); else - pci_nic_init_nofail(nd, "e1000", NULL); + pci_nic_init_nofail(nd, "rtl8139", NULL); } ide_drive_get(hd, MAX_IDE_BUS); @@ -358,6 +362,7 @@ static QEMUMachine pc_machine_v1_1 = { .init = pc_init_pci, .max_cpus = 255, .is_default = 1, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", }; #define PC_COMPAT_1_0 \ @@ -388,6 +393,7 @@ static QEMUMachine pc_machine_v1_0 = { .desc = "Standard PC", .init = pc_init_pci, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_1_0, { /* end of list */ } @@ -402,6 +408,7 @@ static QEMUMachine pc_machine_v0_15 = { .desc = "Standard PC", .init = pc_init_pci, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_0_15, { /* end of list */ } @@ -433,6 +440,7 @@ static QEMUMachine pc_machine_v0_14 = { .desc = "Standard PC", .init = pc_init_pci, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_0_14, { @@ -465,6 +473,7 @@ static QEMUMachine pc_machine_v0_13 = { .desc = "Standard PC", .init = pc_init_pci_no_kvmclock, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_0_13, { @@ -501,6 +510,7 @@ static QEMUMachine pc_machine_v0_12 = { .desc = "Standard PC", .init = pc_init_pci_no_kvmclock, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_0_12, { @@ -533,6 +543,7 @@ static QEMUMachine pc_machine_v0_11 = { .desc = "Standard PC, qemu 0.11", .init = pc_init_pci_no_kvmclock, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_0_11, { @@ -553,6 +564,7 @@ static QEMUMachine pc_machine_v0_10 = { .desc = "Standard PC, qemu 0.10", .init = pc_init_pci_no_kvmclock, .max_cpus = 255, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { PC_COMPAT_0_11, { @@ -585,6 +597,7 @@ static QEMUMachine isapc_machine = { .desc = "ISA-only PC", .init = pc_init_isa, .max_cpus = 1, + .default_machine_opts = "accel=kvm,kernel_irqchip=on", .compat_props = (GlobalProperty[]) { { .driver = "pc-sysfw", @@ -29,8 +29,12 @@ #include "net.h" #include "sysemu.h" #include "loader.h" +#include "hw/pc.h" +#include "kvm.h" +#include "device-assignment.h" #include "range.h" #include "qmp-commands.h" +#include "msi.h" //#define DEBUG_PCI #ifdef DEBUG_PCI @@ -351,6 +355,7 @@ static int get_pci_config_device(QEMUFile *f, void *pv, size_t size) memcpy(s->config, config, size); pci_update_mappings(s); + msi_post_load(s); g_free(config); return 0; @@ -538,6 +543,83 @@ static int pci_parse_devaddr(const char *addr, int *domp, int *busp, return 0; } +/* + * Parse device seg and bdf in device assignment command: + * + * -pcidevice host=[seg:]bus:dev.func + * + * Parse [seg:]<bus>:<slot>.<func> return -1 on error + */ +int pci_parse_host_devaddr(const char *addr, int *segp, int *busp, + int *slotp, int *funcp) +{ + const char *p; + char *e; + int val; + int seg = 0, bus = 0, slot = 0, func = 0; + + /* parse optional seg */ + p = addr; + val = 0; + while (1) { + p = strchr(p, ':'); + if (p) { + val++; + p++; + } else + break; + } + if (val <= 0 || val > 2) + return -1; + + p = addr; + if (val == 2) { + val = strtoul(p, &e, 16); + if (e == p) + return -1; + if (*e == ':') { + seg = val; + p = e + 1; + } + } else + seg = 0; + + + /* parse bdf */ + val = strtoul(p, &e, 16); + if (e == p) + return -1; + if (*e == ':') { + bus = val; + p = e + 1; + val = strtoul(p, &e, 16); + if (e == p) + return -1; + if (*e == '.') { + slot = val; + p = e + 1; + val = strtoul(p, &e, 16); + if (e == p) + return -1; + func = val; + } else + return -1; + } else + return -1; + + if (seg > 0xffff || bus > 0xff || slot > 0x1f || func > 0x7) + return -1; + + if (*e) + return -1; + + *segp = seg; + *busp = bus; + *slotp = slot; + *funcp = func; + return 0; +} + int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, unsigned *slotp) { @@ -1029,6 +1111,14 @@ void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l) d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask); d->config[addr + i] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */ } + +#ifdef CONFIG_KVM_DEVICE_ASSIGNMENT + if (kvm_enabled() && kvm_irqchip_in_kernel() && + addr >= PIIX_CONFIG_IRQ_ROUTE && + addr < PIIX_CONFIG_IRQ_ROUTE + 4) + assigned_dev_update_irqs(); +#endif /* CONFIG_KVM_DEVICE_ASSIGNMENT */ + if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) || ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) || ranges_overlap(addr, l, PCI_ROM_ADDRESS1, 4) || @@ -1059,6 +1149,11 @@ static void pci_set_irq(void *opaque, int irq_num, int level) pci_change_irq_level(pci_dev, irq_num, change); } +int pci_map_irq(PCIDevice *pci_dev, int pin) +{ + return pci_dev->bus->map_irq(pci_dev, pin); +} + /***********************************************************/ /* monitor info on PCI */ @@ -6,6 +6,7 @@ #include "qdev.h" #include "memory.h" #include "dma.h" +#include "kvm.h" /* PCI includes legacy ISA access. */ #include "isa.h" @@ -133,6 +134,9 @@ enum { QEMU_PCI_CAP_SLOTID = (1 << QEMU_PCI_SLOTID_BITNR), }; +typedef int (*msix_mask_notifier_func)(PCIDevice *, unsigned vector, + int masked); + #define TYPE_PCI_DEVICE "pci-device" #define PCI_DEVICE(obj) \ OBJECT_CHECK(PCIDevice, (obj), TYPE_PCI_DEVICE) @@ -243,12 +247,29 @@ struct PCIDevice { bool has_rom; MemoryRegion rom; uint32_t rom_bar; + + /* MSI entries */ + int msi_entries_nr; + struct KVMMsiMessage *msi_irq_entries; + + /* How much space does an MSIX table need. */ + /* The spec requires giving the table structure + * a 4K aligned region all by itself. Align it to + * target pages so that drivers can do passthrough + * on the rest of the region. */ + target_phys_addr_t msix_page_size; + + KVMMsiMessage *msix_irq_entries; + + msix_mask_notifier_func msix_mask_notifier; }; void pci_register_bar(PCIDevice *pci_dev, int region_num, uint8_t attr, MemoryRegion *memory); pcibus_t pci_get_bar_addr(PCIDevice *pci_dev, int region_num); +int pci_map_irq(PCIDevice *pci_dev, int pin); + int pci_add_capability(PCIDevice *pdev, uint8_t cap_id, uint8_t offset, uint8_t size); @@ -314,6 +335,9 @@ PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr); int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, unsigned *slotp); +int pci_parse_host_devaddr(const char *addr, int *segp, int *busp, + int *slotp, int *funcp); + void pci_device_deassert_intx(PCIDevice *dev); static inline void diff --git a/hw/piix_pci.c b/hw/piix_pci.c index 09e84f59b..937a590cb 100644 --- a/hw/piix_pci.c +++ b/hw/piix_pci.c @@ -249,6 +249,8 @@ static int i440fx_initfn(PCIDevice *dev) return 0; } +static PIIX3State *piix3_dev; + static PCIBus *i440fx_common_init(const char *device_name, PCII440FXState **pi440fx_state, int *piix3_devfn, @@ -327,6 +329,7 @@ static PCIBus *i440fx_common_init(const char *device_name, ram_size = 255; (*pi440fx_state)->dev.config[0x57]=ram_size; + piix3_dev = piix3; i440fx_update_memory_mappings(f); return b; @@ -412,6 +415,13 @@ static void piix3_write_config(PCIDevice *dev, } } +int piix_get_irq(int pin) +{ + if (piix3_dev) + return piix3_dev->dev.config[0x60+pin]; + return 0; +} + static void piix3_write_config_xen(PCIDevice *dev, uint32_t address, uint32_t val, int len) { diff --git a/hw/testdev.c b/hw/testdev.c new file mode 100644 index 000000000..21125d54e --- /dev/null +++ b/hw/testdev.c @@ -0,0 +1,154 @@ +#include <sys/mman.h> +#include "hw.h" +#include "qdev.h" +#include "isa.h" + +struct testdev { + ISADevice dev; + MemoryRegion iomem; + CharDriverState *chr; +}; + +#define TYPE_TESTDEV "testdev" +#define TESTDEV(obj) \ + OBJECT_CHECK(struct testdev, (obj), TYPE_TESTDEV) + +static void test_device_serial_write(void *opaque, uint32_t addr, uint32_t data) +{ + struct testdev *dev = opaque; + uint8_t buf[1] = { data }; + + if (dev->chr) { + qemu_chr_fe_write(dev->chr, buf, 1); + } +} + +static void test_device_exit(void *opaque, uint32_t addr, uint32_t data) +{ + exit(data); +} + +static uint32_t test_device_memsize_read(void *opaque, uint32_t addr) +{ + return ram_size; +} + +static void test_device_irq_line(void *opaque, uint32_t addr, uint32_t data) +{ + struct testdev *dev = opaque; + + qemu_set_irq(isa_get_irq(&dev->dev, addr - 0x2000), !!data); +} + +static uint32 test_device_ioport_data; + +static void test_device_ioport_write(void *opaque, uint32_t addr, uint32_t data) +{ + test_device_ioport_data = data; +} + +static uint32_t test_device_ioport_read(void *opaque, uint32_t addr) +{ + return test_device_ioport_data; +} + +static void test_device_flush_page(void *opaque, uint32_t addr, uint32_t data) +{ + target_phys_addr_t len = 4096; + void *a = cpu_physical_memory_map(data & ~0xffful, &len, 0); + + mprotect(a, 4096, PROT_NONE); + mprotect(a, 4096, PROT_READ|PROT_WRITE); + cpu_physical_memory_unmap(a, len, 0, 0); +} + +static char *iomem_buf; + +static uint32_t test_iomem_readb(void *opaque, target_phys_addr_t addr) +{ + return iomem_buf[addr]; +} + +static uint32_t test_iomem_readw(void *opaque, target_phys_addr_t addr) +{ + return *(uint16_t*)(iomem_buf + addr); +} + +static uint32_t test_iomem_readl(void *opaque, target_phys_addr_t addr) +{ + return *(uint32_t*)(iomem_buf + addr); +} + +static void test_iomem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + iomem_buf[addr] = val; +} + +static void test_iomem_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + *(uint16_t*)(iomem_buf + addr) = val; +} + +static void test_iomem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + *(uint32_t*)(iomem_buf + addr) = val; +} + +static const MemoryRegionOps test_iomem_ops = { + .old_mmio = { + .read = { test_iomem_readb, test_iomem_readw, test_iomem_readl, }, + .write = { test_iomem_writeb, test_iomem_writew, test_iomem_writel, }, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int init_test_device(ISADevice *isa) +{ + struct testdev *dev = DO_UPCAST(struct testdev, dev, isa); + + register_ioport_write(0xf1, 1, 1, test_device_serial_write, dev); + register_ioport_write(0xf4, 1, 4, test_device_exit, dev); + register_ioport_read(0xd1, 1, 4, test_device_memsize_read, dev); + register_ioport_read(0xe0, 1, 1, test_device_ioport_read, dev); + register_ioport_write(0xe0, 1, 1, test_device_ioport_write, dev); + register_ioport_read(0xe0, 1, 2, test_device_ioport_read, dev); + register_ioport_write(0xe0, 1, 2, test_device_ioport_write, dev); + register_ioport_read(0xe0, 1, 4, test_device_ioport_read, dev); + register_ioport_write(0xe0, 1, 4, test_device_ioport_write, dev); + register_ioport_write(0xe4, 1, 4, test_device_flush_page, dev); + register_ioport_write(0x2000, 24, 1, test_device_irq_line, NULL); + iomem_buf = g_malloc0(0x10000); + memory_region_init_io(&dev->iomem, &test_iomem_ops, dev, + "testdev", 0x10000); + memory_region_add_subregion(isa_address_space(&dev->dev), 0xff000000, + &dev->iomem); + return 0; +} + +static Property testdev_isa_properties[] = { + DEFINE_PROP_CHR("chardev", struct testdev, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void testdev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *k = ISA_DEVICE_CLASS(klass); + + k->init = init_test_device; + dc->props = testdev_isa_properties; +} + +static TypeInfo testdev_info = { + .name = "testdev", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(struct testdev), + .class_init = testdev_class_init, +}; + +static void testdev_register_types(void) +{ + type_register_static(&testdev_info); +} + +type_init(testdev_register_types) diff --git a/hw/vga_int.h b/hw/vga_int.h index d244d8ff9..6f696b69b 100644 --- a/hw/vga_int.h +++ b/hw/vga_int.h @@ -31,8 +31,8 @@ /* bochs VBE support */ #define CONFIG_BOCHS_VBE -#define VBE_DISPI_MAX_XRES 1600 -#define VBE_DISPI_MAX_YRES 1200 +#define VBE_DISPI_MAX_XRES 2560 +#define VBE_DISPI_MAX_YRES 1600 #define VBE_DISPI_MAX_BPP 32 #define VBE_DISPI_INDEX_ID 0x0 @@ -209,7 +209,7 @@ void vga_init_vbe(VGACommonState *s, MemoryRegion *address_space); extern const uint8_t sr_mask[8]; extern const uint8_t gr_mask[16]; -#define VGA_RAM_SIZE (8192 * 1024) +#define VGA_RAM_SIZE (16 * 1024 * 1024) #define VGABIOS_FILENAME "vgabios.bin" #define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c index 2bfdc4097..9f5e6759b 100644 --- a/hw/virtio-pci.c +++ b/hw/virtio-pci.c @@ -541,6 +541,57 @@ static void virtio_pci_guest_notifier_read(void *opaque) } } +static int virtio_pci_mask_vq(PCIDevice *dev, unsigned vector, + VirtQueue *vq, int masked) +{ + EventNotifier *notifier = virtio_queue_get_guest_notifier(vq); + int r = kvm_set_irqfd(dev->msix_irq_entries[vector].gsi, + event_notifier_get_fd(notifier), + !masked); + if (r < 0) { + return (r == -ENOSYS) ? 0 : r; + } + if (masked) { + qemu_set_fd_handler(event_notifier_get_fd(notifier), + virtio_pci_guest_notifier_read, NULL, vq); + } else { + qemu_set_fd_handler(event_notifier_get_fd(notifier), + NULL, NULL, NULL); + } + return 0; +} + +static int virtio_pci_mask_notifier(PCIDevice *dev, unsigned vector, + int masked) +{ + VirtIOPCIProxy *proxy = container_of(dev, VirtIOPCIProxy, pci_dev); + VirtIODevice *vdev = proxy->vdev; + int r, n; + + for (n = 0; n < VIRTIO_PCI_QUEUE_MAX; n++) { + if (!virtio_queue_get_num(vdev, n)) { + break; + } + if (virtio_queue_vector(vdev, n) != vector) { + continue; + } + r = virtio_pci_mask_vq(dev, vector, virtio_get_queue(vdev, n), masked); + if (r < 0) { + goto undo; + } + } + return 0; +undo: + while (--n >= 0) { + if (virtio_queue_vector(vdev, n) != vector) { + continue; + } + virtio_pci_mask_vq(dev, vector, virtio_get_queue(vdev, n), !masked); + } + return r; +} + + static int virtio_pci_set_guest_notifier(void *opaque, int n, bool assign) { VirtIOPCIProxy *proxy = opaque; @@ -557,6 +608,9 @@ static int virtio_pci_set_guest_notifier(void *opaque, int n, bool assign) } else { qemu_set_fd_handler(event_notifier_get_fd(notifier), NULL, NULL, NULL); + /* Test and clear notifier before closing it, + * in case poll callback didn't have time to run. */ + virtio_pci_guest_notifier_read(vq); event_notifier_cleanup(notifier); } @@ -575,6 +629,13 @@ static int virtio_pci_set_guest_notifiers(void *opaque, bool assign) VirtIODevice *vdev = proxy->vdev; int r, n; + /* Must unset mask notifier while guest notifier + * is still assigned */ + if (kvm_irqchip_in_kernel() && !assign) { + r = msix_unset_mask_notifier(&proxy->pci_dev); + assert(r >= 0); + } + for (n = 0; n < VIRTIO_PCI_QUEUE_MAX; n++) { if (!virtio_queue_get_num(vdev, n)) { break; @@ -586,6 +647,16 @@ static int virtio_pci_set_guest_notifiers(void *opaque, bool assign) } } + /* Must set mask notifier after guest notifier + * has been assigned */ + if (kvm_irqchip_in_kernel() && assign) { + r = msix_set_mask_notifier(&proxy->pci_dev, + virtio_pci_mask_notifier); + if (r < 0) { + goto assign_error; + } + } + return 0; assign_error: @@ -593,6 +664,11 @@ assign_error: while (--n >= 0) { virtio_pci_set_guest_notifier(opaque, n, !assign); } + + if (!assign) { + msix_set_mask_notifier(&proxy->pci_dev, + virtio_pci_mask_notifier); + } return r; } @@ -78,6 +78,7 @@ struct KVMState int pit_state2; int xsave, xcrs; int many_ioeventfds; + int intx_set_mask; /* The man page (and posix) say ioctl numbers are signed int, but * they're not. Linux, glibc and *BSD all treat ioctl numbers as * unsigned, and treating them as signed here can break things */ @@ -889,8 +890,8 @@ static void kvm_init_irq_routing(KVMState *s) kvm_arch_init_irq_routing(s); } -static void kvm_add_routing_entry(KVMState *s, - struct kvm_irq_routing_entry *entry) +void kvm_add_routing_entry(KVMState *s, + struct kvm_irq_routing_entry *entry) { struct kvm_irq_routing_entry *new; int n, size; @@ -939,6 +940,12 @@ int kvm_irqchip_commit_routes(KVMState *s) static void kvm_init_irq_routing(KVMState *s) { } + +int kvm_irqchip_commit_routes(KVMState *s) +{ + return -ENOSYS; +} + #endif /* !KVM_CAP_IRQ_ROUTING */ static int kvm_irqchip_create(KVMState *s) @@ -1072,6 +1079,8 @@ int kvm_init(void) s->pit_state2 = kvm_check_extension(s, KVM_CAP_PIT_STATE2); #endif + s->intx_set_mask = kvm_check_extension(s, KVM_CAP_PCI_2_3); + ret = kvm_arch_init(s); if (ret < 0) { goto err; @@ -1426,6 +1435,11 @@ int kvm_has_gsi_routing(void) #endif } +int kvm_has_intx_set_mask(void) +{ + return kvm_state->intx_set_mask; +} + int kvm_allows_irq0_override(void) { return !kvm_irqchip_in_kernel() || kvm_has_gsi_routing(); @@ -1698,6 +1712,23 @@ int kvm_set_ioeventfd_pio_word(int fd, uint16_t addr, uint16_t val, bool assign) return 0; } +int kvm_set_irqfd(int gsi, int fd, bool assigned) +{ + struct kvm_irqfd irqfd = { + .fd = fd, + .gsi = gsi, + .flags = assigned ? 0 : KVM_IRQFD_FLAG_DEASSIGN, + }; + int r; + if (!kvm_enabled() || !kvm_irqchip_in_kernel()) + return -ENOSYS; + + r = kvm_vm_ioctl(kvm_state, KVM_IRQFD, &irqfd); + if (r < 0) + return r; + return 0; +} + int kvm_on_sigbus_vcpu(CPUArchState *env, int code, void *addr) { return kvm_arch_on_sigbus_vcpu(env, code, addr); @@ -1707,3 +1738,6 @@ int kvm_on_sigbus(int code, void *addr) { return kvm_arch_on_sigbus(code, addr); } + +#undef PAGE_SIZE +#include "qemu-kvm.c" diff --git a/kvm-stub.c b/kvm-stub.c index 47c573d6f..b630322f4 100644 --- a/kvm-stub.c +++ b/kvm-stub.c @@ -16,6 +16,8 @@ #include "gdbstub.h" #include "kvm.h" +KVMState *kvm_state; + int kvm_init_vcpu(CPUArchState *env) { return -ENOSYS; @@ -119,6 +121,42 @@ int kvm_set_ioeventfd_mmio(int fd, uint32_t adr, uint32_t val, bool assign, uint return -ENOSYS; } +int kvm_has_gsi_routing(void) +{ + return 0; +} + +int kvm_get_irq_route_gsi(void) +{ + return -ENOSYS; +} + +int kvm_msi_message_add(KVMMsiMessage *msg) +{ + return -ENOSYS; +} + +int kvm_msi_message_del(KVMMsiMessage *msg) +{ + return -ENOSYS; +} + +int kvm_msi_message_update(KVMMsiMessage *old, KVMMsiMessage *new) +{ + return -ENOSYS; +} + +int kvm_irqchip_commit_routes(KVMState *s) +{ + return -ENOSYS; +} + +int kvm_irqchip_set_irq(KVMState *s, int irq, int level) +{ + assert(0); + return -ENOSYS; +} + int kvm_on_sigbus_vcpu(CPUArchState *env, int code, void *addr) { return 1; @@ -128,3 +166,8 @@ int kvm_on_sigbus(int code, void *addr) { return 1; } + +int kvm_set_irqfd(int gsi, int fd, bool assigned) +{ + return -ENOSYS; +} @@ -54,9 +54,10 @@ int kvm_has_robust_singlestep(void); int kvm_has_debugregs(void); int kvm_has_xsave(void); int kvm_has_xcrs(void); -int kvm_has_pit_state2(void); int kvm_has_many_ioeventfds(void); +int kvm_has_pit_state2(void); int kvm_has_gsi_routing(void); +int kvm_has_intx_set_mask(void); int kvm_allows_irq0_override(void); @@ -85,6 +86,7 @@ int kvm_set_signal_mask(CPUArchState *env, const sigset_t *sigset); int kvm_on_sigbus_vcpu(CPUArchState *env, int code, void *addr); int kvm_on_sigbus(int code, void *addr); +#endif /* NEED_CPU_H */ /* internal API */ @@ -96,6 +98,7 @@ int kvm_ioctl(KVMState *s, int type, ...); int kvm_vm_ioctl(KVMState *s, int type, ...); +#ifdef NEED_CPU_H int kvm_vcpu_ioctl(CPUArchState *env, int type, ...); /* Arch specific hooks */ @@ -211,5 +214,30 @@ int kvm_physical_memory_addr_from_host(KVMState *s, void *ram_addr, int kvm_set_ioeventfd_mmio(int fd, uint32_t adr, uint32_t val, bool assign, uint32_t size); +int kvm_set_irqfd(int gsi, int fd, bool assigned); + int kvm_set_ioeventfd_pio_word(int fd, uint16_t adr, uint16_t val, bool assign); + +typedef struct KVMMsiMessage { + uint32_t gsi; + uint32_t addr_lo; + uint32_t addr_hi; + uint32_t data; +} KVMMsiMessage; + +int kvm_get_irq_route_gsi(void); + +int kvm_msi_message_add(KVMMsiMessage *msg); +int kvm_msi_message_del(KVMMsiMessage *msg); +int kvm_msi_message_update(KVMMsiMessage *old, KVMMsiMessage *new); + +#ifndef NEED_CPU_H +int kvm_irqchip_set_irq(KVMState *s, int irq, int level); +int kvm_irqchip_commit_routes(KVMState *s); +#endif + +#ifdef NEED_CPU_H +#include "qemu-kvm.h" +#endif + #endif @@ -779,6 +779,27 @@ static void do_info_registers(Monitor *mon) #endif } +static void do_cpu_set_nr(Monitor *mon, const QDict *qdict) +{ + int state, value; + const char *status; + + status = qdict_get_str(qdict, "state"); + value = qdict_get_int(qdict, "cpu"); + + if (!strcmp(status, "online")) + state = 1; + else if (!strcmp(status, "offline")) + state = 0; + else { + monitor_printf(mon, "invalid status: %s\n", status); + return; + } +#if defined(TARGET_I386) || defined(TARGET_X86_64) + qemu_system_cpu_hot_add(value, state); +#endif +} + static void do_info_jit(Monitor *mon) { dump_exec_info((FILE *)mon, monitor_fprintf); diff --git a/pc-bios/vgabios-cirrus.bin b/pc-bios/vgabios-cirrus.bin Binary files differindex 424dd0c70..7626ea551 100644 --- a/pc-bios/vgabios-cirrus.bin +++ b/pc-bios/vgabios-cirrus.bin diff --git a/pc-bios/vgabios-stdvga.bin b/pc-bios/vgabios-stdvga.bin Binary files differindex 5123c5fd0..1e4fc33cc 100644 --- a/pc-bios/vgabios-stdvga.bin +++ b/pc-bios/vgabios-stdvga.bin diff --git a/pc-bios/vgabios-vmware.bin b/pc-bios/vgabios-vmware.bin Binary files differindex 5e8c06b22..9633d9af9 100644 --- a/pc-bios/vgabios-vmware.bin +++ b/pc-bios/vgabios-vmware.bin diff --git a/pc-bios/vgabios.bin b/pc-bios/vgabios.bin Binary files differindex 892a2b537..d04033ea1 100644 --- a/pc-bios/vgabios.bin +++ b/pc-bios/vgabios.bin diff --git a/qemu-config.c b/qemu-config.c index be84a0347..5ba19e2aa 100644 --- a/qemu-config.c +++ b/qemu-config.c @@ -113,6 +113,10 @@ static QemuOptsList qemu_drive_opts = { .name = "copy-on-read", .type = QEMU_OPT_BOOL, .help = "copy read data from backing file into image file", + },{ + .name = "boot", + .type = QEMU_OPT_BOOL, + .help = "(deprecated, ignored)", }, { /* end of list */ } }, diff --git a/qemu-kvm.c b/qemu-kvm.c new file mode 100644 index 000000000..133143cce --- /dev/null +++ b/qemu-kvm.c @@ -0,0 +1,317 @@ +/* + * qemu/kvm integration + * + * Copyright (C) 2006-2008 Qumranet Technologies + * + * Licensed under the terms of the GNU GPL version 2 or higher. + */ +#include "config.h" +#include "config-host.h" + +#include <assert.h> +#include <string.h> +#include "hw/hw.h" +#include "sysemu.h" +#include "qemu-common.h" +#include "console.h" +#include "block.h" +#include "compatfd.h" +#include "gdbstub.h" +#include "monitor.h" +#include "cpus.h" + +#include "qemu-kvm.h" + +#define EXPECTED_KVM_API_VERSION 12 + +#if EXPECTED_KVM_API_VERSION != KVM_API_VERSION +#error libkvm: userspace and kernel version mismatch +#endif + +#define ALIGN(x, y) (((x)+(y)-1) & ~((y)-1)) + +#ifdef KVM_CAP_IRQ_ROUTING +static inline void clear_gsi(KVMState *s, unsigned int gsi) +{ + uint32_t *bitmap = s->used_gsi_bitmap; + + if (gsi < s->max_gsi) { + bitmap[gsi / 32] &= ~(1U << (gsi % 32)); + } else { + DPRINTF("Invalid GSI %u\n", gsi); + } +} +#endif + +#ifdef KVM_CAP_DEVICE_ASSIGNMENT +int kvm_assign_pci_device(KVMState *s, + struct kvm_assigned_pci_dev *assigned_dev) +{ + return kvm_vm_ioctl(s, KVM_ASSIGN_PCI_DEVICE, assigned_dev); +} + +static int kvm_old_assign_irq(KVMState *s, + struct kvm_assigned_irq *assigned_irq) +{ + return kvm_vm_ioctl(s, KVM_ASSIGN_IRQ, assigned_irq); +} + +int kvm_device_intx_set_mask(KVMState *s, uint32_t dev_id, bool masked) +{ + struct kvm_assigned_pci_dev assigned_dev; + + assigned_dev.assigned_dev_id = dev_id; + assigned_dev.flags = masked ? KVM_DEV_ASSIGN_MASK_INTX : 0; + return kvm_vm_ioctl(s, KVM_ASSIGN_SET_INTX_MASK, &assigned_dev); +} + +#ifdef KVM_CAP_ASSIGN_DEV_IRQ +int kvm_assign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq) +{ + int ret; + + ret = kvm_ioctl(s, KVM_CHECK_EXTENSION, KVM_CAP_ASSIGN_DEV_IRQ); + if (ret > 0) { + return kvm_vm_ioctl(s, KVM_ASSIGN_DEV_IRQ, assigned_irq); + } + + return kvm_old_assign_irq(s, assigned_irq); +} + +int kvm_deassign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq) +{ + return kvm_vm_ioctl(s, KVM_DEASSIGN_DEV_IRQ, assigned_irq); +} +#else +int kvm_assign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq) +{ + return kvm_old_assign_irq(s, assigned_irq); +} +#endif +#endif + +#ifdef KVM_CAP_DEVICE_DEASSIGNMENT +int kvm_deassign_pci_device(KVMState *s, + struct kvm_assigned_pci_dev *assigned_dev) +{ + return kvm_vm_ioctl(s, KVM_DEASSIGN_PCI_DEVICE, assigned_dev); +} +#endif + +int kvm_del_routing_entry(struct kvm_irq_routing_entry *entry) +{ +#ifdef KVM_CAP_IRQ_ROUTING + KVMState *s = kvm_state; + struct kvm_irq_routing_entry *e, *p; + int i, gsi, found = 0; + + gsi = entry->gsi; + + for (i = 0; i < s->irq_routes->nr; ++i) { + e = &s->irq_routes->entries[i]; + if (e->type == entry->type && e->gsi == gsi) { + switch (e->type) { + case KVM_IRQ_ROUTING_IRQCHIP:{ + if (e->u.irqchip.irqchip == + entry->u.irqchip.irqchip + && e->u.irqchip.pin == entry->u.irqchip.pin) { + p = &s->irq_routes->entries[--s->irq_routes->nr]; + *e = *p; + found = 1; + } + break; + } + case KVM_IRQ_ROUTING_MSI:{ + if (e->u.msi.address_lo == + entry->u.msi.address_lo + && e->u.msi.address_hi == + entry->u.msi.address_hi + && e->u.msi.data == entry->u.msi.data) { + p = &s->irq_routes->entries[--s->irq_routes->nr]; + *e = *p; + found = 1; + } + break; + } + default: + break; + } + if (found) { + /* If there are no other users of this GSI + * mark it available in the bitmap */ + for (i = 0; i < s->irq_routes->nr; i++) { + e = &s->irq_routes->entries[i]; + if (e->gsi == gsi) + break; + } + if (i == s->irq_routes->nr) { + clear_gsi(s, gsi); + } + + return 0; + } + } + } + return -ESRCH; +#else + return -ENOSYS; +#endif +} + +int kvm_update_routing_entry(struct kvm_irq_routing_entry *entry, + struct kvm_irq_routing_entry *newentry) +{ +#ifdef KVM_CAP_IRQ_ROUTING + KVMState *s = kvm_state; + struct kvm_irq_routing_entry *e; + int i; + + if (entry->gsi != newentry->gsi || entry->type != newentry->type) { + return -EINVAL; + } + + for (i = 0; i < s->irq_routes->nr; ++i) { + e = &s->irq_routes->entries[i]; + if (e->type != entry->type || e->gsi != entry->gsi) { + continue; + } + switch (e->type) { + case KVM_IRQ_ROUTING_IRQCHIP: + if (e->u.irqchip.irqchip == entry->u.irqchip.irqchip && + e->u.irqchip.pin == entry->u.irqchip.pin) { + memcpy(&e->u.irqchip, &newentry->u.irqchip, + sizeof e->u.irqchip); + return 0; + } + break; + case KVM_IRQ_ROUTING_MSI: + if (e->u.msi.address_lo == entry->u.msi.address_lo && + e->u.msi.address_hi == entry->u.msi.address_hi && + e->u.msi.data == entry->u.msi.data) { + memcpy(&e->u.msi, &newentry->u.msi, sizeof e->u.msi); + return 0; + } + break; + default: + break; + } + } + return -ESRCH; +#else + return -ENOSYS; +#endif +} + +int kvm_get_irq_route_gsi(void) +{ +#ifdef KVM_CAP_IRQ_ROUTING + KVMState *s = kvm_state; + int i, bit; + uint32_t *buf = s->used_gsi_bitmap; + + /* Return the lowest unused GSI in the bitmap */ + for (i = 0; i < s->max_gsi / 32; i++) { + bit = ffs(~buf[i]); + if (!bit) { + continue; + } + + return bit - 1 + i * 32; + } + + return -ENOSPC; +#else + return -ENOSYS; +#endif +} + +#ifdef KVM_CAP_IRQ_ROUTING +static void kvm_msi_routing_entry(struct kvm_irq_routing_entry *e, + KVMMsiMessage *msg) + +{ + e->gsi = msg->gsi; + e->type = KVM_IRQ_ROUTING_MSI; + e->flags = 0; + e->u.msi.address_lo = msg->addr_lo; + e->u.msi.address_hi = msg->addr_hi; + e->u.msi.data = msg->data; +} +#endif + +int kvm_msi_message_add(KVMMsiMessage *msg) +{ +#ifdef KVM_CAP_IRQ_ROUTING + struct kvm_irq_routing_entry e; + int ret; + + ret = kvm_get_irq_route_gsi(); + if (ret < 0) { + return ret; + } + msg->gsi = ret; + + kvm_msi_routing_entry(&e, msg); + kvm_add_routing_entry(kvm_state, &e); + return 0; +#else + return -ENOSYS; +#endif +} + +int kvm_msi_message_del(KVMMsiMessage *msg) +{ +#ifdef KVM_CAP_IRQ_ROUTING + struct kvm_irq_routing_entry e; + + kvm_msi_routing_entry(&e, msg); + return kvm_del_routing_entry(&e); +#else + return -ENOSYS; +#endif +} + +int kvm_msi_message_update(KVMMsiMessage *old, KVMMsiMessage *new) +{ +#ifdef KVM_CAP_IRQ_ROUTING + struct kvm_irq_routing_entry e1, e2; + int ret; + + new->gsi = old->gsi; + if (memcmp(old, new, sizeof(KVMMsiMessage)) == 0) { + return 0; + } + + kvm_msi_routing_entry(&e1, old); + kvm_msi_routing_entry(&e2, new); + + ret = kvm_update_routing_entry(&e1, &e2); + if (ret < 0) { + return ret; + } + + return 1; +#else + return -ENOSYS; +#endif +} + + +#ifdef KVM_CAP_DEVICE_MSIX +int kvm_assign_set_msix_nr(KVMState *s, struct kvm_assigned_msix_nr *msix_nr) +{ + return kvm_vm_ioctl(s, KVM_ASSIGN_SET_MSIX_NR, msix_nr); +} + +int kvm_assign_set_msix_entry(KVMState *s, + struct kvm_assigned_msix_entry *entry) +{ + return kvm_vm_ioctl(s, KVM_ASSIGN_SET_MSIX_ENTRY, entry); +} +#endif + +#if !defined(TARGET_I386) +void kvm_arch_init_irq_routing(KVMState *s) +{ +} +#endif diff --git a/qemu-kvm.h b/qemu-kvm.h new file mode 100644 index 000000000..3ebbbb6e6 --- /dev/null +++ b/qemu-kvm.h @@ -0,0 +1,112 @@ +/* + * qemu/kvm integration + * + * Copyright (C) 2006-2008 Qumranet Technologies + * + * Licensed under the terms of the GNU GPL version 2 or higher. + */ +#ifndef THE_ORIGINAL_AND_TRUE_QEMU_KVM_H +#define THE_ORIGINAL_AND_TRUE_QEMU_KVM_H + +#include "cpu.h" + +#include <signal.h> +#include <stdlib.h> + +#ifdef CONFIG_KVM + +#include <stdint.h> + +#ifndef __user +#define __user /* temporary, until installed via make headers_install */ +#endif + +#include <linux/kvm.h> + +#include <signal.h> + +/* FIXME: share this number with kvm */ +/* FIXME: or dynamically alloc/realloc regions */ +#define KVM_MAX_NUM_MEM_REGIONS 32u +#define MAX_VCPUS 16 + +#include "kvm.h" + +/*! + * \brief Notifies host kernel about a PCI device to be assigned to a guest + * + * Used for PCI device assignment, this function notifies the host + * kernel about the assigning of the physical PCI device to a guest. + * + * \param kvm Pointer to the current kvm_context + * \param assigned_dev Parameters, like bus, devfn number, etc + */ +int kvm_assign_pci_device(KVMState *s, + struct kvm_assigned_pci_dev *assigned_dev); + +/*! + * \brief Assign IRQ for an assigned device + * + * Used for PCI device assignment, this function assigns IRQ numbers for + * an physical device and guest IRQ handling. + * + * \param kvm Pointer to the current kvm_context + * \param assigned_irq Parameters, like dev id, host irq, guest irq, etc + */ +int kvm_assign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq); + +/*! + * \brief Deassign IRQ for an assigned device + * + * Used for PCI device assignment, this function deassigns IRQ numbers + * for an assigned device. + * + * \param kvm Pointer to the current kvm_context + * \param assigned_irq Parameters, like dev id, host irq, guest irq, etc + */ +int kvm_deassign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq); + +int kvm_device_intx_set_mask(KVMState *s, uint32_t dev_id, bool masked); + +/*! + * \brief Notifies host kernel about a PCI device to be deassigned from a guest + * + * Used for hot remove PCI device, this function notifies the host + * kernel about the deassigning of the physical PCI device from a guest. + * + * \param kvm Pointer to the current kvm_context + * \param assigned_dev Parameters, like bus, devfn number, etc + */ +int kvm_deassign_pci_device(KVMState *s, + struct kvm_assigned_pci_dev *assigned_dev); + +struct kvm_irq_routing_entry; + +void kvm_add_routing_entry(KVMState *s, struct kvm_irq_routing_entry *entry); + +/*! + * \brief Removes a routing from the temporary irq routing table + * + * Remove a routing to the temporary irq routing table. Nothing is + * committed to the running VM. + */ +int kvm_del_routing_entry(struct kvm_irq_routing_entry *entry); + +/*! + * \brief Updates a routing in the temporary irq routing table + * + * Update a routing in the temporary irq routing table + * with a new value. entry type and GSI can not be changed. + * Nothing is committed to the running VM. + */ +int kvm_update_routing_entry(struct kvm_irq_routing_entry *entry, + struct kvm_irq_routing_entry *newentry); + + +int kvm_assign_set_msix_nr(KVMState *s, struct kvm_assigned_msix_nr *msix_nr); +int kvm_assign_set_msix_entry(KVMState *s, + struct kvm_assigned_msix_entry *entry); + +#endif /* CONFIG_KVM */ + +#endif diff --git a/qemu-options.hx b/qemu-options.hx index 8b662648a..125a4da2e 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2743,6 +2743,22 @@ DEF("qtest-log", HAS_ARG, QEMU_OPTION_qtest_log, "-qtest-log LOG specify tracing options\n", QEMU_ARCH_ALL) +DEF("no-kvm", 0, QEMU_OPTION_no_kvm, + "-no-kvm disable KVM hardware virtualization\n", + QEMU_ARCH_ALL) +DEF("no-kvm-irqchip", 0, QEMU_OPTION_no_kvm_irqchip, + "-no-kvm-irqchip disable KVM kernel mode PIC/IOAPIC/LAPIC\n", + QEMU_ARCH_I386) +DEF("no-kvm-pit", 0, QEMU_OPTION_no_kvm_pit, + "-no-kvm-pit disable KVM kernel mode PIT\n", + QEMU_ARCH_I386) +DEF("no-kvm-pit-reinjection", 0, QEMU_OPTION_no_kvm_pit_reinjection, + "-no-kvm-pit-reinjection\n" + " disable KVM kernel mode PIT interrupt reinjection\n", + QEMU_ARCH_I386) +HXCOMM -tdf is deprecated and ignored today +DEF("tdf", 0, QEMU_OPTION_tdf, "", QEMU_ARCH_ALL) + HXCOMM This is the last statement. Insert new options before this line! STEXI @end table diff --git a/roms/vgabios b/roms/vgabios -Subproject 19ea12c230ded95928ecaef0db47a82231c2e48 +Subproject ca056d8e77a534f4f90548bc8cee166a378c145 diff --git a/scripts/qemu-kvm/make-release b/scripts/qemu-kvm/make-release new file mode 100755 index 000000000..2d050fcc7 --- /dev/null +++ b/scripts/qemu-kvm/make-release @@ -0,0 +1,81 @@ +#!/bin/bash -e + +usage() { + echo "usage: $0 [--upload] [--formal] commit [name] [tarball] [user]" + exit 1 +} + +[[ -f ~/.kvmreleaserc ]] && . ~/.kvmreleaserc + +upload= +formal= + +releasedir=~/sf-release +[[ -z "$TMP" ]] && TMP="/tmp" +tmpdir=`mktemp -d "$TMP/qemu-kvm-make-release.XXXXXXXXXX"` +while [[ "$1" = -* ]]; do + opt="$1" + shift + case "$opt" in + --upload) + upload="yes" + ;; + --formal) + formal="yes" + ;; + *) + usage + ;; + esac +done + +commit="$1" +name="$2" + +if [[ -z "$commit" ]]; then + usage +fi + +if [[ -z "$name" ]]; then + name="$commit" +fi + +tarball="$3" +if [[ -z "$tarball" ]]; then + tarball="$releasedir/$name.tar.gz" +fi +#strip trailing .gz if any +tarball=${tarball/%.gz/} + +cd "$(dirname "$0")"/../.. +mkdir -p "$(dirname "$tarball")" +git archive --prefix="$name/" --format=tar "$commit" > "$tarball" + +mtime=`git show --pretty=format:%ct "$commit""^{commit}" -- | head -n 1` +tarargs="--owner=root --group=root" + +mkdir -p "$tmpdir/$name" +git cat-file -p "${commit}:roms" | awk ' { print $4, $3 } ' \ + > "$tmpdir/$name/EXTERNAL_DEPENDENCIES" +touch -d "@$mtime" "$tmpdir/$name/EXTERNAL_DEPENDENCIES" +tar -rf "$tarball" -C "$tmpdir" \ + $tarargs \ + "$name/EXTERNAL_DEPENDENCIES" +rm -rf "$tmpdir" + +if [[ -n "$formal" ]]; then + mkdir -p "$tmpdir/$name" + echo "$name" > "$tmpdir/$name/KVM_VERSION" + touch -d "@$mtime" "$tmpdir/$name/KVM_VERSION" + tar -rf "$tarball" -C "$tmpdir" "$name/KVM_VERSION" \ + $tarargs + rm -rf "$tmpdir" +fi + +rm -f "$tarball.gz" +gzip -9 "$tarball" +tarball="$tarball.gz" + +if [[ -n "$upload" ]]; then + rsync --progress -h "$tarball" avik@frs.sourceforge.net:uploads/ +fi @@ -149,6 +149,9 @@ extern int nb_option_roms; extern const char *prom_envs[MAX_PROM_ENVS]; extern unsigned int nb_prom_envs; +/* acpi */ +void qemu_system_cpu_hot_add(int cpu, int state); + /* pci-hotplug */ void pci_device_hot_add(Monitor *mon, const QDict *qdict); int pci_drive_hot_add(Monitor *mon, const QDict *qdict, diff --git a/target-i386/cpu.h b/target-i386/cpu.h index 8ff3f304c..a1acf48ce 100644 --- a/target-i386/cpu.h +++ b/target-i386/cpu.h @@ -943,6 +943,7 @@ void cpu_x86_update_cr4(CPUX86State *env, uint32_t new_cr4); /* hw/pc.c */ void cpu_smm_update(CPUX86State *env); uint64_t cpu_get_tsc(CPUX86State *env); +CPUArchState *pc_new_cpu(const char *cpu_model); /* used to debug */ #define X86_DUMP_FPU 0x0001 /* dump FPU state too */ @@ -2981,6 +2981,37 @@ int main(int argc, char **argv, char **envp) machine = machine_parse(optarg); } break; + case QEMU_OPTION_no_kvm: + olist = qemu_find_opts("machine"); + qemu_opts_parse(olist, "accel=tcg", 0); + break; +#ifdef CONFIG_KVM_OPTIONS + case QEMU_OPTION_no_kvm_irqchip: { + olist = qemu_find_opts("machine"); + qemu_opts_parse(olist, "kernel_irqchip=off", 0); + break; + } + case QEMU_OPTION_no_kvm_pit: { + fprintf(stderr, "Warning: KVM PIT can no longer be disabled " + "separately.\n"); + break; + } + case QEMU_OPTION_no_kvm_pit_reinjection: { + static GlobalProperty kvm_pit_lost_tick_policy[] = { + { + .driver = "kvm-pit", + .property = "lost_tick_policy", + .value = "discard", + }, + { /* end of list */ } + }; + + fprintf(stderr, "Warning: option deprecated, use " + "lost_tick_policy property of kvm-pit instead.\n"); + qdev_prop_register_global_list(kvm_pit_lost_tick_policy); + break; + } +#endif case QEMU_OPTION_usb: usb_enabled = 1; break; @@ -3064,6 +3095,10 @@ int main(int argc, char **argv, char **envp) case QEMU_OPTION_semihosting: semihosting_enabled = 1; break; + case QEMU_OPTION_tdf: + fprintf(stderr, "Warning: user space PIT time drift fix " + "is no longer supported.\n"); + break; case QEMU_OPTION_name: qemu_name = g_strdup(optarg); { |