/* * QEMU Xen emulation: Grant table 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/lockable.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 "xen_gnttab.h" #include "sysemu/kvm.h" #include "sysemu/kvm_xen.h" #include "hw/xen/interface/memory.h" #include "hw/xen/interface/grant_table.h" #define TYPE_XEN_GNTTAB "xen-gnttab" OBJECT_DECLARE_SIMPLE_TYPE(XenGnttabState, XEN_GNTTAB) #define XEN_PAGE_SHIFT 12 #define XEN_PAGE_SIZE (1ULL << XEN_PAGE_SHIFT) #define ENTRIES_PER_FRAME_V1 (XEN_PAGE_SIZE / sizeof(grant_entry_v1_t)) struct XenGnttabState { /*< private >*/ SysBusDevice busdev; /*< public >*/ QemuMutex gnt_lock; uint32_t nr_frames; uint32_t max_frames; union { grant_entry_v1_t *v1; /* Theoretically, v2 support could be added here. */ } entries; MemoryRegion gnt_frames; MemoryRegion *gnt_aliases; uint64_t *gnt_frame_gpas; }; struct XenGnttabState *xen_gnttab_singleton; static void xen_gnttab_realize(DeviceState *dev, Error **errp) { XenGnttabState *s = XEN_GNTTAB(dev); int i; if (xen_mode != XEN_EMULATE) { error_setg(errp, "Xen grant table support is for Xen emulation"); return; } s->nr_frames = 0; s->max_frames = kvm_xen_get_gnttab_max_frames(); memory_region_init_ram(&s->gnt_frames, OBJECT(dev), "xen:grant_table", XEN_PAGE_SIZE * s->max_frames, &error_abort); memory_region_set_enabled(&s->gnt_frames, true); s->entries.v1 = memory_region_get_ram_ptr(&s->gnt_frames); memset(s->entries.v1, 0, XEN_PAGE_SIZE * s->max_frames); /* Create individual page-sizes aliases for overlays */ s->gnt_aliases = (void *)g_new0(MemoryRegion, s->max_frames); s->gnt_frame_gpas = (void *)g_new(uint64_t, s->max_frames); for (i = 0; i < s->max_frames; i++) { memory_region_init_alias(&s->gnt_aliases[i], OBJECT(dev), NULL, &s->gnt_frames, i * XEN_PAGE_SIZE, XEN_PAGE_SIZE); s->gnt_frame_gpas[i] = INVALID_GPA; } qemu_mutex_init(&s->gnt_lock); xen_gnttab_singleton = s; } static int xen_gnttab_post_load(void *opaque, int version_id) { XenGnttabState *s = XEN_GNTTAB(opaque); uint32_t i; for (i = 0; i < s->nr_frames; i++) { if (s->gnt_frame_gpas[i] != INVALID_GPA) { xen_overlay_do_map_page(&s->gnt_aliases[i], s->gnt_frame_gpas[i]); } } return 0; } static bool xen_gnttab_is_needed(void *opaque) { return xen_mode == XEN_EMULATE; } static const VMStateDescription xen_gnttab_vmstate = { .name = "xen_gnttab", .version_id = 1, .minimum_version_id = 1, .needed = xen_gnttab_is_needed, .post_load = xen_gnttab_post_load, .fields = (VMStateField[]) { VMSTATE_UINT32(nr_frames, XenGnttabState), VMSTATE_VARRAY_UINT32(gnt_frame_gpas, XenGnttabState, nr_frames, 0, vmstate_info_uint64, uint64_t), VMSTATE_END_OF_LIST() } }; static void xen_gnttab_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = xen_gnttab_realize; dc->vmsd = &xen_gnttab_vmstate; } static const TypeInfo xen_gnttab_info = { .name = TYPE_XEN_GNTTAB, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XenGnttabState), .class_init = xen_gnttab_class_init, }; void xen_gnttab_create(void) { xen_gnttab_singleton = XEN_GNTTAB(sysbus_create_simple(TYPE_XEN_GNTTAB, -1, NULL)); } static void xen_gnttab_register_types(void) { type_register_static(&xen_gnttab_info); } type_init(xen_gnttab_register_types) int xen_gnttab_map_page(uint64_t idx, uint64_t gfn) { XenGnttabState *s = xen_gnttab_singleton; uint64_t gpa = gfn << XEN_PAGE_SHIFT; if (!s) { return -ENOTSUP; } if (idx >= s->max_frames) { return -EINVAL; } QEMU_IOTHREAD_LOCK_GUARD(); QEMU_LOCK_GUARD(&s->gnt_lock); xen_overlay_do_map_page(&s->gnt_aliases[idx], gpa); s->gnt_frame_gpas[idx] = gpa; if (s->nr_frames <= idx) { s->nr_frames = idx + 1; } return 0; } int xen_gnttab_set_version_op(struct gnttab_set_version *set) { int ret; switch (set->version) { case 1: ret = 0; break; case 2: /* Behave as before set_version was introduced. */ ret = -ENOSYS; break; default: ret = -EINVAL; } set->version = 1; return ret; } int xen_gnttab_get_version_op(struct gnttab_get_version *get) { if (get->dom != DOMID_SELF && get->dom != xen_domid) { return -ESRCH; } get->version = 1; return 0; } int xen_gnttab_query_size_op(struct gnttab_query_size *size) { XenGnttabState *s = xen_gnttab_singleton; if (!s) { return -ENOTSUP; } if (size->dom != DOMID_SELF && size->dom != xen_domid) { size->status = GNTST_bad_domain; return 0; } size->status = GNTST_okay; size->nr_frames = s->nr_frames; size->max_nr_frames = s->max_frames; return 0; }