diff --git a/MAINTAINERS b/MAINTAINERS index 8839f22e3e..38a290e9c2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2384,6 +2384,9 @@ F: include/hw/virtio/virtio-crypto.h virtio based memory device M: David Hildenbrand S: Supported +F: hw/s390x/virtio-ccw-md.c +F: hw/s390x/virtio-ccw-md.h +F: hw/s390x/virtio-ccw-md-stubs.c F: hw/virtio/virtio-md-pci.c F: include/hw/virtio/virtio-md-pci.h F: stubs/virtio-md-pci.c @@ -2395,6 +2398,8 @@ W: https://virtio-mem.gitlab.io/ F: hw/virtio/virtio-mem.c F: hw/virtio/virtio-mem-pci.h F: hw/virtio/virtio-mem-pci.c +F: hw/s390x/virtio-ccw-mem.c +F: hw/s390x/virtio-ccw-mem.h F: include/hw/virtio/virtio-mem.h virtio-snd diff --git a/hw/s390x/Kconfig b/hw/s390x/Kconfig index 82afdaa9dc..02ea199701 100644 --- a/hw/s390x/Kconfig +++ b/hw/s390x/Kconfig @@ -16,3 +16,4 @@ config S390_CCW_VIRTIO select SCLPCONSOLE select VIRTIO_CCW select MSI_NONBROKEN + select VIRTIO_MEM_SUPPORTED diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build index 482fd13420..3bbebfd817 100644 --- a/hw/s390x/meson.build +++ b/hw/s390x/meson.build @@ -12,7 +12,6 @@ s390x_ss.add(files( 's390-pci-inst.c', 's390-skeys.c', 's390-stattrib.c', - 's390-virtio-hcall.c', 'sclp.c', 'sclpcpu.c', 'sclpquiesce.c', @@ -28,7 +27,10 @@ s390x_ss.add(when: 'CONFIG_KVM', if_true: files( s390x_ss.add(when: 'CONFIG_TCG', if_true: files( 'tod-tcg.c', )) -s390x_ss.add(when: 'CONFIG_S390_CCW_VIRTIO', if_true: files('s390-virtio-ccw.c')) +s390x_ss.add(when: 'CONFIG_S390_CCW_VIRTIO', if_true: files( + 's390-virtio-ccw.c', + 's390-hypercall.c', +)) s390x_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('3270-ccw.c')) s390x_ss.add(when: 'CONFIG_VFIO', if_true: files('s390-pci-vfio.c')) @@ -48,8 +50,12 @@ endif virtio_ss.add(when: 'CONFIG_VHOST_SCSI', if_true: files('vhost-scsi-ccw.c')) virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock-ccw.c')) virtio_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs-ccw.c')) +virtio_ss.add(when: 'CONFIG_VIRTIO_MD', if_true: files('virtio-ccw-md.c')) +virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-ccw-mem.c')) s390x_ss.add_all(when: 'CONFIG_VIRTIO_CCW', if_true: virtio_ss) +s390x_ss.add(when: 'CONFIG_VIRTIO_MD', if_false: files('virtio-ccw-md-stubs.c')) + hw_arch += {'s390x': s390x_ss} hw_s390x_modules = {} diff --git a/hw/s390x/s390-hypercall.c b/hw/s390x/s390-hypercall.c new file mode 100644 index 0000000000..ac1b08b2cd --- /dev/null +++ b/hw/s390x/s390-hypercall.c @@ -0,0 +1,85 @@ +/* + * Support for QEMU/KVM hypercalls on s390 + * + * Copyright 2012 IBM Corp. + * Author(s): Cornelia Huck + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "hw/s390x/s390-virtio-ccw.h" +#include "hw/s390x/s390-hypercall.h" +#include "hw/s390x/ioinst.h" +#include "hw/s390x/css.h" +#include "virtio-ccw.h" + +static int handle_virtio_notify(uint64_t mem) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + + if (mem < ms->ram_size) { + /* Early printk */ + return 0; + } + return -EINVAL; +} + +static int handle_virtio_ccw_notify(uint64_t subch_id, uint64_t data) +{ + SubchDev *sch; + VirtIODevice *vdev; + int cssid, ssid, schid, m; + uint16_t vq_idx = data; + + if (ioinst_disassemble_sch_ident(subch_id, &m, &cssid, &ssid, &schid)) { + return -EINVAL; + } + sch = css_find_subch(m, cssid, ssid, schid); + if (!sch || !css_subch_visible(sch)) { + return -EINVAL; + } + + vdev = virtio_ccw_get_vdev(sch); + if (vq_idx >= VIRTIO_QUEUE_MAX || !virtio_queue_get_num(vdev, vq_idx)) { + return -EINVAL; + } + + if (virtio_vdev_has_feature(vdev, VIRTIO_F_NOTIFICATION_DATA)) { + virtio_queue_set_shadow_avail_idx(virtio_get_queue(vdev, vq_idx), + (data >> 16) & 0xFFFF); + } + + virtio_queue_notify(vdev, vq_idx); + return 0; +} + +static uint64_t handle_storage_limit(void) +{ + S390CcwMachineState *s390ms = S390_CCW_MACHINE(qdev_get_machine()); + + return s390_get_memory_limit(s390ms) - 1; +} + +void handle_diag_500(S390CPU *cpu, uintptr_t ra) +{ + CPUS390XState *env = &cpu->env; + const uint64_t subcode = env->regs[1]; + + switch (subcode) { + case DIAG500_VIRTIO_NOTIFY: + env->regs[2] = handle_virtio_notify(env->regs[2]); + break; + case DIAG500_VIRTIO_CCW_NOTIFY: + env->regs[2] = handle_virtio_ccw_notify(env->regs[2], env->regs[3]); + break; + case DIAG500_STORAGE_LIMIT: + env->regs[2] = handle_storage_limit(); + break; + default: + s390_program_interrupt(env, PGM_SPECIFICATION, ra); + } +} diff --git a/hw/s390x/s390-hypercall.h b/hw/s390x/s390-hypercall.h new file mode 100644 index 0000000000..4f07209128 --- /dev/null +++ b/hw/s390x/s390-hypercall.h @@ -0,0 +1,25 @@ +/* + * Support for QEMU/KVM hypercalls on s390x + * + * Copyright IBM Corp. 2012, 2017 + * Author(s): Cornelia Huck + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at + * your option) any later version. See the COPYING file in the top-level + * directory. + */ + +#ifndef HW_S390_HYPERCALL_H +#define HW_S390_HYPERCALL_H + +#include "cpu.h" + +#define DIAG500_VIRTIO_NOTIFY 0 /* legacy, implemented as a NOP */ +#define DIAG500_VIRTIO_RESET 1 /* legacy */ +#define DIAG500_VIRTIO_SET_STATUS 2 /* legacy */ +#define DIAG500_VIRTIO_CCW_NOTIFY 3 /* KVM_S390_VIRTIO_CCW_NOTIFY */ +#define DIAG500_STORAGE_LIMIT 4 + +void handle_diag_500(S390CPU *cpu, uintptr_t ra); + +#endif /* HW_S390_HYPERCALL_H */ diff --git a/hw/s390x/s390-skeys.c b/hw/s390x/s390-skeys.c index c853ab7e3d..dda96ea32a 100644 --- a/hw/s390x/s390-skeys.c +++ b/hw/s390x/s390-skeys.c @@ -11,7 +11,7 @@ #include "qemu/osdep.h" #include "qemu/units.h" -#include "hw/boards.h" +#include "hw/s390x/s390-virtio-ccw.h" #include "hw/qdev-properties.h" #include "hw/s390x/storage-keys.h" #include "qapi/error.h" @@ -251,9 +251,9 @@ static bool qemu_s390_enable_skeys(S390SKeysState *ss) * g_once_init_enter() is good enough. */ if (g_once_init_enter(&initialized)) { - MachineState *machine = MACHINE(qdev_get_machine()); + S390CcwMachineState *s390ms = S390_CCW_MACHINE(qdev_get_machine()); - skeys->key_count = machine->ram_size / TARGET_PAGE_SIZE; + skeys->key_count = s390_get_memory_limit(s390ms) / TARGET_PAGE_SIZE; skeys->keydata = g_malloc0(skeys->key_count); g_once_init_leave(&initialized, 1); } diff --git a/hw/s390x/s390-stattrib-kvm.c b/hw/s390x/s390-stattrib-kvm.c index 508fd558e8..2a8e31718b 100644 --- a/hw/s390x/s390-stattrib-kvm.c +++ b/hw/s390x/s390-stattrib-kvm.c @@ -10,11 +10,12 @@ */ #include "qemu/osdep.h" -#include "hw/boards.h" +#include "hw/s390x/s390-virtio-ccw.h" #include "migration/qemu-file.h" #include "hw/s390x/storage-attributes.h" #include "qemu/error-report.h" #include "system/kvm.h" +#include "system/memory_mapping.h" #include "exec/ram_addr.h" #include "kvm/kvm_s390x.h" #include "qapi/error.h" @@ -84,8 +85,8 @@ static int kvm_s390_stattrib_set_stattr(S390StAttribState *sa, uint8_t *values) { KVMS390StAttribState *sas = KVM_S390_STATTRIB(sa); - MachineState *machine = MACHINE(qdev_get_machine()); - unsigned long max = machine->ram_size / TARGET_PAGE_SIZE; + S390CcwMachineState *s390ms = S390_CCW_MACHINE(qdev_get_machine()); + unsigned long max = s390_get_memory_limit(s390ms) / TARGET_PAGE_SIZE; if (start_gfn + count > max) { error_report("Out of memory bounds when setting storage attributes"); @@ -103,39 +104,57 @@ static int kvm_s390_stattrib_set_stattr(S390StAttribState *sa, static void kvm_s390_stattrib_synchronize(S390StAttribState *sa) { KVMS390StAttribState *sas = KVM_S390_STATTRIB(sa); - MachineState *machine = MACHINE(qdev_get_machine()); - unsigned long max = machine->ram_size / TARGET_PAGE_SIZE; - /* We do not need to reach the maximum buffer size allowed */ - unsigned long cx, len = KVM_S390_SKEYS_MAX / 2; + S390CcwMachineState *s390ms = S390_CCW_MACHINE(qdev_get_machine()); + unsigned long max = s390_get_memory_limit(s390ms) / TARGET_PAGE_SIZE; + unsigned long start_gfn, end_gfn, pages; + GuestPhysBlockList guest_phys_blocks; + GuestPhysBlock *block; int r; struct kvm_s390_cmma_log clog = { .flags = 0, .mask = ~0ULL, }; - if (sas->incoming_buffer) { - for (cx = 0; cx + len <= max; cx += len) { - clog.start_gfn = cx; - clog.count = len; - clog.values = (uint64_t)(sas->incoming_buffer + cx); - r = kvm_vm_ioctl(kvm_state, KVM_S390_SET_CMMA_BITS, &clog); - if (r) { - error_report("KVM_S390_SET_CMMA_BITS failed: %s", strerror(-r)); - return; - } - } - if (cx < max) { - clog.start_gfn = cx; - clog.count = max - cx; - clog.values = (uint64_t)(sas->incoming_buffer + cx); - r = kvm_vm_ioctl(kvm_state, KVM_S390_SET_CMMA_BITS, &clog); - if (r) { - error_report("KVM_S390_SET_CMMA_BITS failed: %s", strerror(-r)); - } - } - g_free(sas->incoming_buffer); - sas->incoming_buffer = NULL; + if (!sas->incoming_buffer) { + return; } + guest_phys_blocks_init(&guest_phys_blocks); + guest_phys_blocks_append(&guest_phys_blocks); + + QTAILQ_FOREACH(block, &guest_phys_blocks.head, next) { + assert(QEMU_IS_ALIGNED(block->target_start, TARGET_PAGE_SIZE)); + assert(QEMU_IS_ALIGNED(block->target_end, TARGET_PAGE_SIZE)); + + start_gfn = block->target_start / TARGET_PAGE_SIZE; + end_gfn = block->target_end / TARGET_PAGE_SIZE; + + while (start_gfn < end_gfn) { + /* Don't exceed the maximum buffer size. */ + pages = MIN(end_gfn - start_gfn, KVM_S390_SKEYS_MAX / 2); + + /* + * If we ever get guest physical memory beyond the configured + * memory limit, something went very wrong. + */ + assert(start_gfn + pages <= max); + + clog.start_gfn = start_gfn; + clog.count = pages; + clog.values = (uint64_t)(sas->incoming_buffer + start_gfn); + r = kvm_vm_ioctl(kvm_state, KVM_S390_SET_CMMA_BITS, &clog); + if (r) { + error_report("KVM_S390_SET_CMMA_BITS failed: %s", strerror(-r)); + goto out; + } + + start_gfn += pages; + } + } + +out: + guest_phys_blocks_free(&guest_phys_blocks); + g_free(sas->incoming_buffer); + sas->incoming_buffer = NULL; } static int kvm_s390_stattrib_set_migrationmode(S390StAttribState *sa, bool val, diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c index b45d8963b3..2be8da2913 100644 --- a/hw/s390x/s390-virtio-ccw.c +++ b/hw/s390x/s390-virtio-ccw.c @@ -16,11 +16,8 @@ #include "exec/ram_addr.h" #include "system/confidential-guest-support.h" #include "hw/boards.h" -#include "hw/s390x/s390-virtio-hcall.h" #include "hw/s390x/sclp.h" #include "hw/s390x/s390_flic.h" -#include "hw/s390x/ioinst.h" -#include "hw/s390x/css.h" #include "virtio-ccw.h" #include "qemu/config-file.h" #include "qemu/ctype.h" @@ -48,6 +45,9 @@ #include "migration/blocker.h" #include "qapi/visitor.h" #include "hw/s390x/cpu-topology.h" +#include "kvm/kvm_s390x.h" +#include "hw/virtio/virtio-md-pci.h" +#include "hw/s390x/virtio-ccw-md.h" #include CONFIG_DEVICES static Error *pv_mig_blocker; @@ -124,70 +124,86 @@ static void subsystem_reset(void) } } -static int virtio_ccw_hcall_notify(const uint64_t *args) +static void s390_set_memory_limit(S390CcwMachineState *s390ms, + uint64_t new_limit) { - uint64_t subch_id = args[0]; - uint64_t data = args[1]; - SubchDev *sch; - VirtIODevice *vdev; - int cssid, ssid, schid, m; - uint16_t vq_idx = data; + uint64_t hw_limit = 0; + int ret = 0; - if (ioinst_disassemble_sch_ident(subch_id, &m, &cssid, &ssid, &schid)) { - return -EINVAL; + assert(!s390ms->memory_limit && new_limit); + if (kvm_enabled()) { + ret = kvm_s390_set_mem_limit(new_limit, &hw_limit); } - sch = css_find_subch(m, cssid, ssid, schid); - if (!sch || !css_subch_visible(sch)) { - return -EINVAL; + if (ret == -E2BIG) { + error_report("host supports a maximum of %" PRIu64 " GB", + hw_limit / GiB); + exit(EXIT_FAILURE); + } else if (ret) { + error_report("setting the guest size failed"); + exit(EXIT_FAILURE); } - - vdev = virtio_ccw_get_vdev(sch); - if (vq_idx >= VIRTIO_QUEUE_MAX || !virtio_queue_get_num(vdev, vq_idx)) { - return -EINVAL; - } - - if (virtio_vdev_has_feature(vdev, VIRTIO_F_NOTIFICATION_DATA)) { - virtio_queue_set_shadow_avail_idx(virtio_get_queue(vdev, vq_idx), - (data >> 16) & 0xFFFF); - } - - virtio_queue_notify(vdev, vq_idx); - return 0; + s390ms->memory_limit = new_limit; } -static int virtio_ccw_hcall_early_printk(const uint64_t *args) +static void s390_set_max_pagesize(S390CcwMachineState *s390ms, + uint64_t pagesize) { - uint64_t mem = args[0]; - MachineState *ms = MACHINE(qdev_get_machine()); - - if (mem < ms->ram_size) { - /* Early printk */ - return 0; + assert(!s390ms->max_pagesize && pagesize); + if (kvm_enabled()) { + kvm_s390_set_max_pagesize(pagesize, &error_fatal); } - return -EINVAL; + s390ms->max_pagesize = pagesize; } -static void virtio_ccw_register_hcalls(void) -{ - s390_register_virtio_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, - virtio_ccw_hcall_notify); - /* Tolerate early printk. */ - s390_register_virtio_hypercall(KVM_S390_VIRTIO_NOTIFY, - virtio_ccw_hcall_early_printk); -} - -static void s390_memory_init(MemoryRegion *ram) +static void s390_memory_init(MachineState *machine) { + S390CcwMachineState *s390ms = S390_CCW_MACHINE(machine); MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = machine->ram; + uint64_t ram_size = memory_region_size(ram); + uint64_t devmem_base, devmem_size; - /* allocate RAM for core */ + if (!QEMU_IS_ALIGNED(ram_size, 1 * MiB)) { + /* + * SCLP cannot possibly expose smaller granularity right now and KVM + * cannot handle smaller granularity. As we don't support NUMA, the + * region size directly corresponds to machine->ram_size, and the region + * is a single RAM memory region. + */ + error_report("ram size must be multiples of 1 MiB"); + exit(EXIT_FAILURE); + } + + devmem_size = 0; + devmem_base = ram_size; +#ifdef CONFIG_MEM_DEVICE + if (machine->ram_size < machine->maxram_size) { + + /* + * Make sure memory devices have a sane default alignment, even + * when weird initial memory sizes are specified. + */ + devmem_base = QEMU_ALIGN_UP(devmem_base, 1 * GiB); + devmem_size = machine->maxram_size - machine->ram_size; + } +#endif + s390_set_memory_limit(s390ms, devmem_base + devmem_size); + + /* Map the initial memory. Must happen after setting the memory limit. */ memory_region_add_subregion(sysmem, 0, ram); + /* Initialize address space for memory devices. */ +#ifdef CONFIG_MEM_DEVICE + if (devmem_size) { + machine_memory_devices_init(machine, devmem_base, devmem_size); + } +#endif /* CONFIG_MEM_DEVICE */ + /* * Configure the maximum page size. As no memory devices were created * yet, this is the page size of initial memory only. */ - s390_set_max_pagesize(qemu_maxrampagesize(), &error_fatal); + s390_set_max_pagesize(s390ms, qemu_maxrampagesize()); /* Initialize storage key device */ s390_skeys_init(); /* Initialize storage attributes device */ @@ -255,7 +271,7 @@ static void ccw_init(MachineState *machine) qdev_realize_and_unref(DEVICE(ms->sclp), NULL, &error_fatal); /* init memory + setup max page size. Required for the CPU model */ - s390_memory_init(machine->ram); + s390_memory_init(machine); /* init CPUs (incl. CPU model) early so s390_has_feature() works */ s390_init_cpus(machine); @@ -285,9 +301,6 @@ static void ccw_init(MachineState *machine) OBJECT(dev)); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); - /* register hypercalls */ - virtio_ccw_register_hcalls(); - s390_enable_css_support(s390_cpu_addr2state(0)); ret = css_create_css_image(VIRTUAL_CSSID, true); @@ -535,11 +548,39 @@ static void s390_machine_reset(MachineState *machine, ResetType type) s390_ipl_clear_reset_request(); } +static void s390_machine_device_pre_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { + virtio_ccw_md_pre_plug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { + error_setg(errp, + "PCI-attached virtio based memory devices not supported"); + } +} + static void s390_machine_device_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { + S390CcwMachineState *s390ms = S390_CCW_MACHINE(hotplug_dev); + if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { s390_cpu_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { + /* + * At this point, the device is realized and set all memdevs mapped, so + * qemu_maxrampagesize() will pick up the page sizes of these memdevs + * as well. Before we plug the device and expose any RAM memory regions + * to the system, make sure we don't exceed the previously set max page + * size. While only relevant for KVM, there is not really any use case + * for this with TCG, so we'll unconditionally reject it. + */ + if (qemu_maxrampagesize() != s390ms->max_pagesize) { + error_setg(errp, "Memory device uses a bigger page size than" + " initial memory"); + return; + } + virtio_ccw_md_plug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); } } @@ -549,9 +590,20 @@ static void s390_machine_device_unplug_request(HotplugHandler *hotplug_dev, if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { error_setg(errp, "CPU hot unplug not supported on this machine"); return; + } else if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { + virtio_ccw_md_unplug_request(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), + errp); } } +static void s390_machine_device_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW)) { + virtio_ccw_md_unplug(VIRTIO_MD_CCW(dev), MACHINE(hotplug_dev), errp); + } + } + static CpuInstanceProperties s390_cpu_index_to_props(MachineState *ms, unsigned cpu_index) { @@ -598,7 +650,9 @@ static const CPUArchIdList *s390_possible_cpu_arch_ids(MachineState *ms) static HotplugHandler *s390_get_hotplug_handler(MachineState *machine, DeviceState *dev) { - if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { + if (object_dynamic_cast(OBJECT(dev), TYPE_CPU) || + object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_CCW) || + object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MD_PCI)) { return HOTPLUG_HANDLER(machine); } return NULL; @@ -758,8 +812,10 @@ static void ccw_machine_class_init(ObjectClass *oc, void *data) mc->possible_cpu_arch_ids = s390_possible_cpu_arch_ids; /* it is overridden with 'host' cpu *in kvm_arch_init* */ mc->default_cpu_type = S390_CPU_TYPE_NAME("qemu"); + hc->pre_plug = s390_machine_device_pre_plug; hc->plug = s390_machine_device_plug; hc->unplug_request = s390_machine_device_unplug_request; + hc->unplug = s390_machine_device_unplug; nc->nmi_monitor_handler = s390_nmi; mc->default_ram_id = "s390.ram"; mc->default_nic = "virtio-net-ccw"; diff --git a/hw/s390x/s390-virtio-hcall.c b/hw/s390x/s390-virtio-hcall.c deleted file mode 100644 index ec7cf8beb3..0000000000 --- a/hw/s390x/s390-virtio-hcall.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Support for virtio hypercalls on s390 - * - * Copyright 2012 IBM Corp. - * Author(s): Cornelia Huck - * - * This work is licensed under the terms of the GNU GPL, version 2 or (at - * your option) any later version. See the COPYING file in the top-level - * directory. - */ - -#include "qemu/osdep.h" -#include "cpu.h" -#include "hw/s390x/s390-virtio-hcall.h" - -#define MAX_DIAG_SUBCODES 255 - -static s390_virtio_fn s390_diag500_table[MAX_DIAG_SUBCODES]; - -void s390_register_virtio_hypercall(uint64_t code, s390_virtio_fn fn) -{ - assert(code < MAX_DIAG_SUBCODES); - assert(!s390_diag500_table[code]); - - s390_diag500_table[code] = fn; -} - -int s390_virtio_hypercall(CPUS390XState *env) -{ - s390_virtio_fn fn; - - if (env->regs[1] < MAX_DIAG_SUBCODES) { - fn = s390_diag500_table[env->regs[1]]; - if (fn) { - env->regs[2] = fn(&env->regs[2]); - return 0; - } - } - - return -EINVAL; -} diff --git a/hw/s390x/s390-virtio-hcall.h b/hw/s390x/s390-virtio-hcall.h deleted file mode 100644 index 3ae6d6ae3a..0000000000 --- a/hw/s390x/s390-virtio-hcall.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Support for virtio hypercalls on s390x - * - * Copyright IBM Corp. 2012, 2017 - * Author(s): Cornelia Huck - * - * This work is licensed under the terms of the GNU GPL, version 2 or (at - * your option) any later version. See the COPYING file in the top-level - * directory. - */ - -#ifndef HW_S390_VIRTIO_HCALL_H -#define HW_S390_VIRTIO_HCALL_H - -#include "standard-headers/asm-s390/virtio-ccw.h" -#include "cpu.h" - -/* The only thing that we need from the old kvm_virtio.h file */ -#define KVM_S390_VIRTIO_NOTIFY 0 - -typedef int (*s390_virtio_fn)(const uint64_t *args); -void s390_register_virtio_hypercall(uint64_t code, s390_virtio_fn fn); -int s390_virtio_hypercall(CPUS390XState *env); - -#endif /* HW_S390_VIRTIO_HCALL_H */ diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c index 8757626b5c..5945c9b1d8 100644 --- a/hw/s390x/sclp.c +++ b/hw/s390x/sclp.c @@ -161,7 +161,11 @@ static void read_SCP_info(SCLPDevice *sclp, SCCB *sccb) read_info->rnsize2 = cpu_to_be32(rnsize); } - /* we don't support standby memory, maxram_size is never exposed */ + /* + * We don't support standby memory. maxram_size is used for sizing the + * memory device region, which is not exposed through SCLP but through + * diag500. + */ rnmax = machine->ram_size >> sclp->increment_size; if (rnmax < 0x10000) { read_info->rnmax = cpu_to_be16(rnmax); @@ -376,10 +380,7 @@ void sclp_service_interrupt(uint32_t sccb) /* qemu object creation and initialization functions */ static void sclp_realize(DeviceState *dev, Error **errp) { - MachineState *machine = MACHINE(qdev_get_machine()); SCLPDevice *sclp = SCLP(dev); - uint64_t hw_limit; - int ret; /* * qdev_device_add searches the sysbus for TYPE_SCLP_EVENTS_BUS. As long @@ -389,14 +390,6 @@ static void sclp_realize(DeviceState *dev, Error **errp) if (!sysbus_realize(SYS_BUS_DEVICE(sclp->event_facility), errp)) { return; } - - ret = s390_set_memory_limit(machine->maxram_size, &hw_limit); - if (ret == -E2BIG) { - error_setg(errp, "host supports a maximum of %" PRIu64 " GB", - hw_limit / GiB); - } else if (ret) { - error_setg(errp, "setting the guest size failed"); - } } static void sclp_memory_init(SCLPDevice *sclp) diff --git a/hw/s390x/virtio-ccw-md-stubs.c b/hw/s390x/virtio-ccw-md-stubs.c new file mode 100644 index 0000000000..e937865550 --- /dev/null +++ b/hw/s390x/virtio-ccw-md-stubs.c @@ -0,0 +1,24 @@ +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/s390x/virtio-ccw-md.h" + +void virtio_ccw_md_pre_plug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp) +{ + error_setg(errp, "virtio based memory devices not supported"); +} + +void virtio_ccw_md_plug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp) +{ + error_setg(errp, "virtio based memory devices not supported"); +} + +void virtio_ccw_md_unplug_request(VirtIOMDCcw *vmd, MachineState *ms, + Error **errp) +{ + error_setg(errp, "virtio based memory devices not supported"); +} + +void virtio_ccw_md_unplug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp) +{ + error_setg(errp, "virtio based memory devices not supported"); +} diff --git a/hw/s390x/virtio-ccw-md.c b/hw/s390x/virtio-ccw-md.c new file mode 100644 index 0000000000..de333282df --- /dev/null +++ b/hw/s390x/virtio-ccw-md.c @@ -0,0 +1,153 @@ +/* + * Virtio CCW support for abstract virtio based memory device + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Authors: + * David Hildenbrand + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/s390x/virtio-ccw-md.h" +#include "hw/mem/memory-device.h" +#include "qapi/error.h" +#include "qemu/error-report.h" + +void virtio_ccw_md_pre_plug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp) +{ + DeviceState *dev = DEVICE(vmd); + HotplugHandler *bus_handler = qdev_get_bus_hotplug_handler(dev); + MemoryDeviceState *md = MEMORY_DEVICE(vmd); + Error *local_err = NULL; + + if (!bus_handler && dev->hotplugged) { + /* + * Without a bus hotplug handler, we cannot control the plug/unplug + * order. We should never reach this point when hotplugging, but + * better add a safety net. + */ + error_setg(errp, "hotplug of virtio based memory devices not supported" + " on this bus."); + return; + } + + /* + * First, see if we can plug this memory device at all. If that + * succeeds, branch of to the actual hotplug handler. + */ + memory_device_pre_plug(md, ms, &local_err); + if (!local_err && bus_handler) { + hotplug_handler_pre_plug(bus_handler, dev, &local_err); + } + error_propagate(errp, local_err); +} + +void virtio_ccw_md_plug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp) +{ + DeviceState *dev = DEVICE(vmd); + HotplugHandler *bus_handler = qdev_get_bus_hotplug_handler(dev); + MemoryDeviceState *md = MEMORY_DEVICE(vmd); + Error *local_err = NULL; + + /* + * Plug the memory device first and then branch off to the actual + * hotplug handler. If that one fails, we can easily undo the memory + * device bits. + */ + memory_device_plug(md, ms); + if (bus_handler) { + hotplug_handler_plug(bus_handler, dev, &local_err); + if (local_err) { + memory_device_unplug(md, ms); + } + } + error_propagate(errp, local_err); +} + +void virtio_ccw_md_unplug_request(VirtIOMDCcw *vmd, MachineState *ms, + Error **errp) +{ + VirtIOMDCcwClass *vmdc = VIRTIO_MD_CCW_GET_CLASS(vmd); + DeviceState *dev = DEVICE(vmd); + HotplugHandler *bus_handler = qdev_get_bus_hotplug_handler(dev); + HotplugHandlerClass *hdc; + Error *local_err = NULL; + + if (!vmdc->unplug_request_check) { + error_setg(errp, + "this virtio based memory devices cannot be unplugged"); + return; + } + + if (!bus_handler) { + error_setg(errp, "hotunplug of virtio based memory devices not" + "supported on this bus"); + return; + } + + vmdc->unplug_request_check(vmd, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + /* + * Forward the async request or turn it into a sync request (handling it + * like qdev_unplug()). + */ + hdc = HOTPLUG_HANDLER_GET_CLASS(bus_handler); + if (hdc->unplug_request) { + hotplug_handler_unplug_request(bus_handler, dev, &local_err); + } else { + virtio_ccw_md_unplug(vmd, ms, &local_err); + if (!local_err) { + object_unparent(OBJECT(dev)); + } + } +} + +void virtio_ccw_md_unplug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp) +{ + DeviceState *dev = DEVICE(vmd); + HotplugHandler *bus_handler = qdev_get_bus_hotplug_handler(dev); + MemoryDeviceState *md = MEMORY_DEVICE(vmd); + Error *local_err = NULL; + + /* Unplug the memory device while it is still realized. */ + memory_device_unplug(md, ms); + + if (bus_handler) { + hotplug_handler_unplug(bus_handler, dev, &local_err); + if (local_err) { + /* Not expected to fail ... but still try to recover. */ + memory_device_plug(md, ms); + error_propagate(errp, local_err); + return; + } + } else { + /* Very unexpected, but let's just try to do the right thing. */ + warn_report("Unexpected unplug of virtio based memory device"); + qdev_unrealize(dev); + } +} + +static const TypeInfo virtio_ccw_md_info = { + .name = TYPE_VIRTIO_MD_CCW, + .parent = TYPE_VIRTIO_CCW_DEVICE, + .instance_size = sizeof(VirtIOMDCcw), + .class_size = sizeof(VirtIOMDCcwClass), + .abstract = true, + .interfaces = (InterfaceInfo[]) { + { TYPE_MEMORY_DEVICE }, + { } + }, +}; + +static void virtio_ccw_md_register(void) +{ + type_register_static(&virtio_ccw_md_info); +} +type_init(virtio_ccw_md_register) diff --git a/hw/s390x/virtio-ccw-md.h b/hw/s390x/virtio-ccw-md.h new file mode 100644 index 0000000000..39ba864c92 --- /dev/null +++ b/hw/s390x/virtio-ccw-md.h @@ -0,0 +1,44 @@ +/* + * Virtio CCW support for abstract virtio based memory device + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Authors: + * David Hildenbrand + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#ifndef HW_S390X_VIRTIO_CCW_MD_H +#define HW_S390X_VIRTIO_CCW_MD_H + +#include "virtio-ccw.h" +#include "qom/object.h" + +/* + * virtio-md-ccw: This extends VirtioCcwDevice. + */ +#define TYPE_VIRTIO_MD_CCW "virtio-md-ccw" + +OBJECT_DECLARE_TYPE(VirtIOMDCcw, VirtIOMDCcwClass, VIRTIO_MD_CCW) + +struct VirtIOMDCcwClass { + /* private */ + VirtIOCCWDeviceClass parent; + + /* public */ + void (*unplug_request_check)(VirtIOMDCcw *vmd, Error **errp); +}; + +struct VirtIOMDCcw { + VirtioCcwDevice parent_obj; +}; + +void virtio_ccw_md_pre_plug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp); +void virtio_ccw_md_plug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp); +void virtio_ccw_md_unplug_request(VirtIOMDCcw *vmd, MachineState *ms, + Error **errp); +void virtio_ccw_md_unplug(VirtIOMDCcw *vmd, MachineState *ms, Error **errp); + +#endif /* HW_S390X_VIRTIO_CCW_MD_H */ diff --git a/hw/s390x/virtio-ccw-mem.c b/hw/s390x/virtio-ccw-mem.c new file mode 100644 index 0000000000..90fd89f015 --- /dev/null +++ b/hw/s390x/virtio-ccw-mem.c @@ -0,0 +1,225 @@ +/* + * virtio-mem CCW implementation + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Authors: + * David Hildenbrand + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "virtio-ccw-mem.h" +#include "hw/mem/memory-device.h" +#include "qapi/qapi-events-machine.h" +#include "qapi/qapi-events-misc.h" + +static void virtio_ccw_mem_realize(VirtioCcwDevice *ccw_dev, Error **errp) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(ccw_dev); + DeviceState *vdev = DEVICE(&dev->vdev); + + qdev_realize(vdev, BUS(&ccw_dev->bus), errp); +} + +static void virtio_ccw_mem_set_addr(MemoryDeviceState *md, uint64_t addr, + Error **errp) +{ + object_property_set_uint(OBJECT(md), VIRTIO_MEM_ADDR_PROP, addr, errp); +} + +static uint64_t virtio_ccw_mem_get_addr(const MemoryDeviceState *md) +{ + return object_property_get_uint(OBJECT(md), VIRTIO_MEM_ADDR_PROP, + &error_abort); +} + +static MemoryRegion *virtio_ccw_mem_get_memory_region(MemoryDeviceState *md, + Error **errp) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(md); + VirtIOMEM *vmem = &dev->vdev; + VirtIOMEMClass *vmc = VIRTIO_MEM_GET_CLASS(vmem); + + return vmc->get_memory_region(vmem, errp); +} + +static void virtio_ccw_mem_decide_memslots(MemoryDeviceState *md, + unsigned int limit) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(md); + VirtIOMEM *vmem = VIRTIO_MEM(&dev->vdev); + VirtIOMEMClass *vmc = VIRTIO_MEM_GET_CLASS(vmem); + + vmc->decide_memslots(vmem, limit); +} + +static unsigned int virtio_ccw_mem_get_memslots(MemoryDeviceState *md) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(md); + VirtIOMEM *vmem = VIRTIO_MEM(&dev->vdev); + VirtIOMEMClass *vmc = VIRTIO_MEM_GET_CLASS(vmem); + + return vmc->get_memslots(vmem); +} + +static uint64_t virtio_ccw_mem_get_plugged_size(const MemoryDeviceState *md, + Error **errp) +{ + return object_property_get_uint(OBJECT(md), VIRTIO_MEM_SIZE_PROP, + errp); +} + +static void virtio_ccw_mem_fill_device_info(const MemoryDeviceState *md, + MemoryDeviceInfo *info) +{ + VirtioMEMDeviceInfo *vi = g_new0(VirtioMEMDeviceInfo, 1); + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(md); + VirtIOMEM *vmem = &dev->vdev; + VirtIOMEMClass *vpc = VIRTIO_MEM_GET_CLASS(vmem); + DeviceState *vdev = DEVICE(md); + + if (vdev->id) { + vi->id = g_strdup(vdev->id); + } + + /* let the real device handle everything else */ + vpc->fill_device_info(vmem, vi); + + info->u.virtio_mem.data = vi; + info->type = MEMORY_DEVICE_INFO_KIND_VIRTIO_MEM; +} + +static uint64_t virtio_ccw_mem_get_min_alignment(const MemoryDeviceState *md) +{ + return object_property_get_uint(OBJECT(md), VIRTIO_MEM_BLOCK_SIZE_PROP, + &error_abort); +} + +static void virtio_ccw_mem_size_change_notify(Notifier *notifier, void *data) +{ + VirtIOMEMCcw *dev = container_of(notifier, VirtIOMEMCcw, + size_change_notifier); + DeviceState *vdev = DEVICE(dev); + char *qom_path = object_get_canonical_path(OBJECT(dev)); + const uint64_t * const size_p = data; + + qapi_event_send_memory_device_size_change(vdev->id, *size_p, qom_path); + g_free(qom_path); +} + +static void virtio_ccw_mem_unplug_request_check(VirtIOMDCcw *vmd, Error **errp) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(vmd); + VirtIOMEM *vmem = &dev->vdev; + VirtIOMEMClass *vpc = VIRTIO_MEM_GET_CLASS(vmem); + + vpc->unplug_request_check(vmem, errp); +} + +static void virtio_ccw_mem_get_requested_size(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(obj); + + object_property_get(OBJECT(&dev->vdev), name, v, errp); +} + +static void virtio_ccw_mem_set_requested_size(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(obj); + DeviceState *vdev = DEVICE(obj); + + /* + * If we passed virtio_ccw_mem_unplug_request_check(), making sure that + * the requested size is 0, don't allow modifying the requested size + * anymore, otherwise the VM might end up hotplugging memory before + * handling the unplug request. + */ + if (vdev->pending_deleted_event) { + error_setg(errp, "'%s' cannot be changed if the device is in the" + " process of unplug", name); + return; + } + + object_property_set(OBJECT(&dev->vdev), name, v, errp); +} + +static const Property virtio_ccw_mem_properties[] = { + DEFINE_PROP_BIT("ioeventfd", VirtioCcwDevice, flags, + VIRTIO_CCW_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_UINT32("max_revision", VirtioCcwDevice, max_rev, + VIRTIO_CCW_MAX_REV), +}; + +static void virtio_ccw_mem_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtIOCCWDeviceClass *k = VIRTIO_CCW_DEVICE_CLASS(klass); + MemoryDeviceClass *mdc = MEMORY_DEVICE_CLASS(klass); + VirtIOMDCcwClass *vmdc = VIRTIO_MD_CCW_CLASS(klass); + + k->realize = virtio_ccw_mem_realize; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + device_class_set_props(dc, virtio_ccw_mem_properties); + + mdc->get_addr = virtio_ccw_mem_get_addr; + mdc->set_addr = virtio_ccw_mem_set_addr; + mdc->get_plugged_size = virtio_ccw_mem_get_plugged_size; + mdc->get_memory_region = virtio_ccw_mem_get_memory_region; + mdc->decide_memslots = virtio_ccw_mem_decide_memslots; + mdc->get_memslots = virtio_ccw_mem_get_memslots; + mdc->fill_device_info = virtio_ccw_mem_fill_device_info; + mdc->get_min_alignment = virtio_ccw_mem_get_min_alignment; + + vmdc->unplug_request_check = virtio_ccw_mem_unplug_request_check; +} + +static void virtio_ccw_mem_instance_init(Object *obj) +{ + VirtIOMEMCcw *dev = VIRTIO_MEM_CCW(obj); + VirtIOMEMClass *vmc; + VirtIOMEM *vmem; + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VIRTIO_MEM); + + dev->size_change_notifier.notify = virtio_ccw_mem_size_change_notify; + vmem = &dev->vdev; + vmc = VIRTIO_MEM_GET_CLASS(vmem); + /* + * We never remove the notifier again, as we expect both devices to + * disappear at the same time. + */ + vmc->add_size_change_notifier(vmem, &dev->size_change_notifier); + + object_property_add_alias(obj, VIRTIO_MEM_BLOCK_SIZE_PROP, + OBJECT(&dev->vdev), VIRTIO_MEM_BLOCK_SIZE_PROP); + object_property_add_alias(obj, VIRTIO_MEM_SIZE_PROP, OBJECT(&dev->vdev), + VIRTIO_MEM_SIZE_PROP); + object_property_add(obj, VIRTIO_MEM_REQUESTED_SIZE_PROP, "size", + virtio_ccw_mem_get_requested_size, + virtio_ccw_mem_set_requested_size, NULL, NULL); +} + +static const TypeInfo virtio_ccw_mem = { + .name = TYPE_VIRTIO_MEM_CCW, + .parent = TYPE_VIRTIO_MD_CCW, + .instance_size = sizeof(VirtIOMEMCcw), + .instance_init = virtio_ccw_mem_instance_init, + .class_init = virtio_ccw_mem_class_init, +}; + +static void virtio_ccw_mem_register_types(void) +{ + type_register_static(&virtio_ccw_mem); +} +type_init(virtio_ccw_mem_register_types) diff --git a/hw/s390x/virtio-ccw-mem.h b/hw/s390x/virtio-ccw-mem.h new file mode 100644 index 0000000000..738ab2c744 --- /dev/null +++ b/hw/s390x/virtio-ccw-mem.h @@ -0,0 +1,34 @@ +/* + * Virtio MEM CCW device + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Authors: + * David Hildenbrand + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#ifndef HW_S390X_VIRTIO_CCW_MEM_H +#define HW_S390X_VIRTIO_CCW_MEM_H + +#include "virtio-ccw-md.h" +#include "hw/virtio/virtio-mem.h" +#include "qom/object.h" + +typedef struct VirtIOMEMCcw VirtIOMEMCcw; + +/* + * virtio-mem-ccw: This extends VirtIOMDCcw + */ +#define TYPE_VIRTIO_MEM_CCW "virtio-mem-ccw" +DECLARE_INSTANCE_CHECKER(VirtIOMEMCcw, VIRTIO_MEM_CCW, TYPE_VIRTIO_MEM_CCW) + +struct VirtIOMEMCcw { + VirtIOMDCcw parent_obj; + VirtIOMEM vdev; + Notifier size_change_notifier; +}; + +#endif /* HW_S390X_VIRTIO_CCW_MEM_H */ diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig index 70c77e183d..7648a2d68d 100644 --- a/hw/virtio/Kconfig +++ b/hw/virtio/Kconfig @@ -29,6 +29,7 @@ config VIRTIO_MMIO config VIRTIO_CCW bool select VIRTIO + select VIRTIO_MD_SUPPORTED config VIRTIO_BALLOON bool diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index cf362a4aa5..b1a003736b 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -61,6 +61,8 @@ static uint32_t virtio_mem_default_thp_size(void) } else if (qemu_real_host_page_size() == 64 * KiB) { default_thp_size = 512 * MiB; } +#elif defined(__s390x__) + default_thp_size = 1 * MiB; #endif return default_thp_size; @@ -168,7 +170,7 @@ static bool virtio_mem_has_shared_zeropage(RAMBlock *rb) * necessary (as the section size can change). But it's more likely that the * section size will rather get smaller and not bigger over time. */ -#if defined(TARGET_X86_64) || defined(TARGET_I386) +#if defined(TARGET_X86_64) || defined(TARGET_I386) || defined(TARGET_S390X) #define VIRTIO_MEM_USABLE_EXTENT (2 * (128 * MiB)) #elif defined(TARGET_ARM) #define VIRTIO_MEM_USABLE_EXTENT (2 * (512 * MiB)) @@ -956,6 +958,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) VirtIOMEM *vmem = VIRTIO_MEM(dev); uint64_t page_size; RAMBlock *rb; + Object *obj; int ret; if (!vmem->memdev) { @@ -1121,7 +1124,28 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) vmstate_register_any(VMSTATE_IF(vmem), &vmstate_virtio_mem_device_early, vmem); } - qemu_register_resettable(OBJECT(vmem)); + + /* + * We only want to unplug all memory to start with a clean slate when + * it is safe for the guest -- during system resets that call + * qemu_devices_reset(). + * + * We'll filter out selected qemu_devices_reset() calls used for other + * purposes, like resetting all devices during wakeup from suspend on + * x86 based on the reset type passed to qemu_devices_reset(). + * + * Unplugging all memory during simple device resets can result in the VM + * unexpectedly losing RAM, corrupting VM state. + * + * Simple device resets (or resets triggered by getting a parent device + * reset) must not change the state of plugged memory blocks. Therefore, + * we need a dedicated reset object that only gets called during + * qemu_devices_reset(). + */ + obj = object_new(TYPE_VIRTIO_MEM_SYSTEM_RESET); + vmem->system_reset = VIRTIO_MEM_SYSTEM_RESET(obj); + vmem->system_reset->vmem = vmem; + qemu_register_resettable(obj); /* * Set ourselves as RamDiscardManager before the plug handler maps the @@ -1141,7 +1165,10 @@ static void virtio_mem_device_unrealize(DeviceState *dev) * found via an address space anymore. Unset ourselves. */ memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL); - qemu_unregister_resettable(OBJECT(vmem)); + + qemu_unregister_resettable(OBJECT(vmem->system_reset)); + object_unref(OBJECT(vmem->system_reset)); + if (vmem->early_migration) { vmstate_unregister(VMSTATE_IF(vmem), &vmstate_virtio_mem_device_early, vmem); @@ -1840,38 +1867,12 @@ static void virtio_mem_unplug_request_check(VirtIOMEM *vmem, Error **errp) } } -static ResettableState *virtio_mem_get_reset_state(Object *obj) -{ - VirtIOMEM *vmem = VIRTIO_MEM(obj); - return &vmem->reset_state; -} - -static void virtio_mem_system_reset_hold(Object *obj, ResetType type) -{ - VirtIOMEM *vmem = VIRTIO_MEM(obj); - - /* - * When waking up from standby/suspend-to-ram, do not unplug any memory. - */ - if (type == RESET_TYPE_WAKEUP) { - return; - } - - /* - * During usual resets, we will unplug all memory and shrink the usable - * region size. This is, however, not possible in all scenarios. Then, - * the guest has to deal with this manually (VIRTIO_MEM_REQ_UNPLUG_ALL). - */ - virtio_mem_unplug_all(vmem); -} - static void virtio_mem_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); VirtIOMEMClass *vmc = VIRTIO_MEM_CLASS(klass); RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass); - ResettableClass *rc = RESETTABLE_CLASS(klass); device_class_set_props(dc, virtio_mem_properties); dc->vmsd = &vmstate_virtio_mem; @@ -1898,9 +1899,6 @@ static void virtio_mem_class_init(ObjectClass *klass, void *data) rdmc->replay_discarded = virtio_mem_rdm_replay_discarded; rdmc->register_listener = virtio_mem_rdm_register_listener; rdmc->unregister_listener = virtio_mem_rdm_unregister_listener; - - rc->get_state = virtio_mem_get_reset_state; - rc->phases.hold = virtio_mem_system_reset_hold; } static const TypeInfo virtio_mem_info = { @@ -1923,3 +1921,48 @@ static void virtio_register_types(void) } type_init(virtio_register_types) + +OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(VirtioMemSystemReset, virtio_mem_system_reset, VIRTIO_MEM_SYSTEM_RESET, OBJECT, { TYPE_RESETTABLE_INTERFACE }, { }) + +static void virtio_mem_system_reset_init(Object *obj) +{ +} + +static void virtio_mem_system_reset_finalize(Object *obj) +{ +} + +static ResettableState *virtio_mem_system_reset_get_state(Object *obj) +{ + VirtioMemSystemReset *vmem_reset = VIRTIO_MEM_SYSTEM_RESET(obj); + + return &vmem_reset->reset_state; +} + +static void virtio_mem_system_reset_hold(Object *obj, ResetType type) +{ + VirtioMemSystemReset *vmem_reset = VIRTIO_MEM_SYSTEM_RESET(obj); + VirtIOMEM *vmem = vmem_reset->vmem; + + /* + * When waking up from standby/suspend-to-ram, do not unplug any memory. + */ + if (type == RESET_TYPE_WAKEUP) { + return; + } + + /* + * During usual resets, we will unplug all memory and shrink the usable + * region size. This is, however, not possible in all scenarios. Then, + * the guest has to deal with this manually (VIRTIO_MEM_REQ_UNPLUG_ALL). + */ + virtio_mem_unplug_all(vmem); +} + +static void virtio_mem_system_reset_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->get_state = virtio_mem_system_reset_get_state; + rc->phases.hold = virtio_mem_system_reset_hold; +} diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h index 996864a34e..599740a998 100644 --- a/include/hw/s390x/s390-virtio-ccw.h +++ b/include/hw/s390x/s390-virtio-ccw.h @@ -29,10 +29,19 @@ struct S390CcwMachineState { bool dea_key_wrap; bool pv; uint8_t loadparm[8]; + uint64_t memory_limit; + uint64_t max_pagesize; SCLPDevice *sclp; }; +static inline uint64_t s390_get_memory_limit(S390CcwMachineState *s390ms) +{ + /* We expect to be called only after the limit was set. */ + assert(s390ms->memory_limit); + return s390ms->memory_limit; +} + #define S390_PTF_REASON_NONE (0x00 << 8) #define S390_PTF_REASON_DONE (0x01 << 8) #define S390_PTF_REASON_BUSY (0x02 << 8) diff --git a/include/hw/virtio/virtio-mem.h b/include/hw/virtio/virtio-mem.h index b23946b770..bc4f787772 100644 --- a/include/hw/virtio/virtio-mem.h +++ b/include/hw/virtio/virtio-mem.h @@ -25,6 +25,10 @@ OBJECT_DECLARE_TYPE(VirtIOMEM, VirtIOMEMClass, VIRTIO_MEM) +#define TYPE_VIRTIO_MEM_SYSTEM_RESET "virtio-mem-system-reset" + +OBJECT_DECLARE_SIMPLE_TYPE(VirtioMemSystemReset, VIRTIO_MEM_SYSTEM_RESET) + #define VIRTIO_MEM_MEMDEV_PROP "memdev" #define VIRTIO_MEM_NODE_PROP "node" #define VIRTIO_MEM_SIZE_PROP "size" @@ -117,8 +121,15 @@ struct VirtIOMEM { /* listeners to notify on plug/unplug activity. */ QLIST_HEAD(, RamDiscardListener) rdl_list; - /* State of the resettable container */ + /* Catch system resets -> qemu_devices_reset() only. */ + VirtioMemSystemReset *system_reset; +}; + +struct VirtioMemSystemReset { + Object parent; + ResettableState reset_state; + VirtIOMEM *vmem; }; struct VirtIOMEMClass { diff --git a/target/s390x/cpu-system.c b/target/s390x/cpu-system.c index 2ba2598ae0..e9f8e7cc72 100644 --- a/target/s390x/cpu-system.c +++ b/target/s390x/cpu-system.c @@ -255,21 +255,6 @@ unsigned int s390_cpu_set_state(uint8_t cpu_state, S390CPU *cpu) return s390_count_running_cpus(); } -int s390_set_memory_limit(uint64_t new_limit, uint64_t *hw_limit) -{ - if (kvm_enabled()) { - return kvm_s390_set_mem_limit(new_limit, hw_limit); - } - return 0; -} - -void s390_set_max_pagesize(uint64_t pagesize, Error **errp) -{ - if (kvm_enabled()) { - kvm_s390_set_max_pagesize(pagesize, errp); - } -} - void s390_cmma_reset(void) { if (kvm_enabled()) { diff --git a/target/s390x/cpu.h b/target/s390x/cpu.h index 5ef61b1f75..5b7992deda 100644 --- a/target/s390x/cpu.h +++ b/target/s390x/cpu.h @@ -881,8 +881,6 @@ static inline void s390_do_cpu_load_normal(CPUState *cs, run_on_cpu_data arg) /* cpu.c */ void s390_crypto_reset(void); -int s390_set_memory_limit(uint64_t new_limit, uint64_t *hw_limit); -void s390_set_max_pagesize(uint64_t pagesize, Error **errp); void s390_cmma_reset(void); void s390_enable_css_support(S390CPU *cpu); void s390_do_cpu_set_diag318(CPUState *cs, run_on_cpu_data arg); diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c index d4ba83eff9..db645a4813 100644 --- a/target/s390x/kvm/kvm.c +++ b/target/s390x/kvm/kvm.c @@ -49,8 +49,9 @@ #include "hw/s390x/ebcdic.h" #include "exec/memattrs.h" #include "hw/s390x/s390-virtio-ccw.h" -#include "hw/s390x/s390-virtio-hcall.h" +#include "hw/s390x/s390-hypercall.h" #include "target/s390x/kvm/pv.h" +#include CONFIG_DEVICES #define kvm_vm_check_mem_attr(s, attr) \ kvm_vm_check_attr(s, KVM_S390_VM_MEM_CTRL, attr) @@ -1491,20 +1492,6 @@ static int handle_e3(S390CPU *cpu, struct kvm_run *run, uint8_t ipbl) return r; } -static int handle_hypercall(S390CPU *cpu, struct kvm_run *run) -{ - CPUS390XState *env = &cpu->env; - int ret; - - ret = s390_virtio_hypercall(env); - if (ret == -EINVAL) { - kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION); - return 0; - } - - return ret; -} - static void kvm_handle_diag_288(S390CPU *cpu, struct kvm_run *run) { uint64_t r1, r3; @@ -1600,9 +1587,11 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb) case DIAG_SET_CONTROL_PROGRAM_CODES: handle_diag_318(cpu, run); break; +#ifdef CONFIG_S390_CCW_VIRTIO case DIAG_KVM_HYPERCALL: - r = handle_hypercall(cpu, run); + handle_diag_500(cpu, RA_IGNORED); break; +#endif /* CONFIG_S390_CCW_VIRTIO */ case DIAG_KVM_BREAKPOINT: r = handle_sw_breakpoint(cpu, run); break; diff --git a/target/s390x/kvm/pv.c b/target/s390x/kvm/pv.c index 69c1811e15..b191a4a68a 100644 --- a/target/s390x/kvm/pv.c +++ b/target/s390x/kvm/pv.c @@ -133,7 +133,7 @@ bool s390_pv_vm_try_disable_async(S390CcwMachineState *ms) * If the feature is not present or if the VM is not larger than 2 GiB, * KVM_PV_ASYNC_CLEANUP_PREPARE fill fail; no point in attempting it. */ - if ((MACHINE(ms)->maxram_size <= 2 * GiB) || + if (s390_get_memory_limit(ms) <= 2 * GiB || !kvm_check_extension(kvm_state, KVM_CAP_S390_PROTECTED_ASYNC_DISABLE)) { return false; } diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c index ea25197d73..0245451472 100644 --- a/target/s390x/tcg/misc_helper.c +++ b/target/s390x/tcg/misc_helper.c @@ -36,13 +36,14 @@ #include "system/cpus.h" #include "system/system.h" #include "hw/s390x/ebcdic.h" -#include "hw/s390x/s390-virtio-hcall.h" +#include "hw/s390x/s390-hypercall.h" #include "hw/s390x/sclp.h" #include "hw/s390x/s390_flic.h" #include "hw/s390x/ioinst.h" #include "hw/s390x/s390-pci-inst.h" #include "hw/boards.h" #include "hw/s390x/tod.h" +#include CONFIG_DEVICES #endif /* #define DEBUG_HELPER */ @@ -116,12 +117,15 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num) uint64_t r; switch (num) { +#ifdef CONFIG_S390_CCW_VIRTIO case 0x500: - /* KVM hypercall */ + /* QEMU/KVM hypercall */ bql_lock(); - r = s390_virtio_hypercall(env); + handle_diag_500(env_archcpu(env), GETPC()); bql_unlock(); + r = 0; break; +#endif /* CONFIG_S390_CCW_VIRTIO */ case 0x44: /* yield */ r = 0;