| /* |
| * QEMU Xen emulation: Grant table support |
| * |
| * Copyright © 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| * |
| * Authors: David Woodhouse <dwmw2@infradead.org> |
| * |
| * 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; |
| } |