/* * QEMU Xen emulation: Shared/overlay pages support * * Copyright © 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Authors: David Woodhouse * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "qemu/host-utils.h" #include "qemu/module.h" #include "qemu/main-loop.h" #include "qapi/error.h" #include "qom/object.h" #include "exec/target_page.h" #include "exec/address-spaces.h" #include "migration/vmstate.h" #include "hw/sysbus.h" #include "hw/xen/xen.h" #include "xen_overlay.h" #include "sysemu/kvm.h" #include "sysemu/kvm_xen.h" #include #include "hw/xen/interface/memory.h" #define TYPE_XEN_OVERLAY "xen-overlay" OBJECT_DECLARE_SIMPLE_TYPE(XenOverlayState, XEN_OVERLAY) #define XEN_PAGE_SHIFT 12 #define XEN_PAGE_SIZE (1ULL << XEN_PAGE_SHIFT) struct XenOverlayState { /*< private >*/ SysBusDevice busdev; /*< public >*/ MemoryRegion shinfo_mem; void *shinfo_ptr; uint64_t shinfo_gpa; bool long_mode; }; struct XenOverlayState *xen_overlay_singleton; void xen_overlay_do_map_page(MemoryRegion *page, uint64_t gpa) { /* * Xen allows guests to map the same page as many times as it likes * into guest physical frames. We don't, because it would be hard * to track and restore them all. One mapping of each page is * perfectly sufficient for all known guests... and we've tested * that theory on a few now in other implementations. dwmw2. */ if (memory_region_is_mapped(page)) { if (gpa == INVALID_GPA) { memory_region_del_subregion(get_system_memory(), page); } else { /* Just move it */ memory_region_set_address(page, gpa); } } else if (gpa != INVALID_GPA) { memory_region_add_subregion_overlap(get_system_memory(), gpa, page, 0); } } /* KVM is the only existing back end for now. Let's not overengineer it yet. */ static int xen_overlay_set_be_shinfo(uint64_t gfn) { struct kvm_xen_hvm_attr xa = { .type = KVM_XEN_ATTR_TYPE_SHARED_INFO, .u.shared_info.gfn = gfn, }; return kvm_vm_ioctl(kvm_state, KVM_XEN_HVM_SET_ATTR, &xa); } static void xen_overlay_realize(DeviceState *dev, Error **errp) { XenOverlayState *s = XEN_OVERLAY(dev); if (xen_mode != XEN_EMULATE) { error_setg(errp, "Xen overlay page support is for Xen emulation"); return; } memory_region_init_ram(&s->shinfo_mem, OBJECT(dev), "xen:shared_info", XEN_PAGE_SIZE, &error_abort); memory_region_set_enabled(&s->shinfo_mem, true); s->shinfo_ptr = memory_region_get_ram_ptr(&s->shinfo_mem); s->shinfo_gpa = INVALID_GPA; s->long_mode = false; memset(s->shinfo_ptr, 0, XEN_PAGE_SIZE); } static int xen_overlay_pre_save(void *opaque) { /* * Fetch the kernel's idea of long_mode to avoid the race condition * where the guest has set the hypercall page up in 64-bit mode but * not yet made a hypercall by the time migration happens, so qemu * hasn't yet noticed. */ return xen_sync_long_mode(); } static int xen_overlay_post_load(void *opaque, int version_id) { XenOverlayState *s = opaque; if (s->shinfo_gpa != INVALID_GPA) { xen_overlay_do_map_page(&s->shinfo_mem, s->shinfo_gpa); xen_overlay_set_be_shinfo(s->shinfo_gpa >> XEN_PAGE_SHIFT); } if (s->long_mode) { xen_set_long_mode(true); } return 0; } static bool xen_overlay_is_needed(void *opaque) { return xen_mode == XEN_EMULATE; } static const VMStateDescription xen_overlay_vmstate = { .name = "xen_overlay", .version_id = 1, .minimum_version_id = 1, .needed = xen_overlay_is_needed, .pre_save = xen_overlay_pre_save, .post_load = xen_overlay_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT64(shinfo_gpa, XenOverlayState), VMSTATE_BOOL(long_mode, XenOverlayState), VMSTATE_END_OF_LIST() } }; static void xen_overlay_reset(DeviceState *dev) { kvm_xen_soft_reset(); } static void xen_overlay_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, xen_overlay_reset); dc->realize = xen_overlay_realize; dc->vmsd = &xen_overlay_vmstate; } static const TypeInfo xen_overlay_info = { .name = TYPE_XEN_OVERLAY, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XenOverlayState), .class_init = xen_overlay_class_init, }; void xen_overlay_create(void) { xen_overlay_singleton = XEN_OVERLAY(sysbus_create_simple(TYPE_XEN_OVERLAY, -1, NULL)); /* If xen_domid wasn't explicitly set, at least make sure it isn't zero. */ if (xen_domid == DOMID_QEMU) { xen_domid = 1; }; } static void xen_overlay_register_types(void) { type_register_static(&xen_overlay_info); } type_init(xen_overlay_register_types) int xen_overlay_map_shinfo_page(uint64_t gpa) { XenOverlayState *s = xen_overlay_singleton; int ret; if (!s) { return -ENOENT; } assert(bql_locked()); if (s->shinfo_gpa) { /* If removing shinfo page, turn the kernel magic off first */ ret = xen_overlay_set_be_shinfo(INVALID_GFN); if (ret) { return ret; } } xen_overlay_do_map_page(&s->shinfo_mem, gpa); if (gpa != INVALID_GPA) { ret = xen_overlay_set_be_shinfo(gpa >> XEN_PAGE_SHIFT); if (ret) { return ret; } } s->shinfo_gpa = gpa; return 0; } void *xen_overlay_get_shinfo_ptr(void) { XenOverlayState *s = xen_overlay_singleton; if (!s) { return NULL; } return s->shinfo_ptr; } int xen_sync_long_mode(void) { int ret; struct kvm_xen_hvm_attr xa = { .type = KVM_XEN_ATTR_TYPE_LONG_MODE, }; if (!xen_overlay_singleton) { return -ENOENT; } ret = kvm_vm_ioctl(kvm_state, KVM_XEN_HVM_GET_ATTR, &xa); if (!ret) { xen_overlay_singleton->long_mode = xa.u.long_mode; } return ret; } int xen_set_long_mode(bool long_mode) { int ret; struct kvm_xen_hvm_attr xa = { .type = KVM_XEN_ATTR_TYPE_LONG_MODE, .u.long_mode = long_mode, }; if (!xen_overlay_singleton) { return -ENOENT; } ret = kvm_vm_ioctl(kvm_state, KVM_XEN_HVM_SET_ATTR, &xa); if (!ret) { xen_overlay_singleton->long_mode = xa.u.long_mode; } return ret; } bool xen_is_long_mode(void) { return xen_overlay_singleton && xen_overlay_singleton->long_mode; }