MacOS provides a framework (library) that allows any vmm to implement a
paravirtualized 3d graphics passthrough to the host metal stack called
ParavirtualizedGraphics.Framework (PVG). The library abstracts away
almost every aspect of the paravirtualized device model and only provides
and receives callbacks on MMIO access as well as to share memory address
space between the VM and PVG.
This patch implements a QEMU device that drives PVG for the VMApple
variant of it.
Signed-off-by: Alexander Graf <graf@amazon.com>
Co-authored-by: Alexander Graf <graf@amazon.com>
Subsequent changes:
* Cherry-pick/rebase conflict fixes, API use updates.
* Moved from hw/vmapple/ (useful outside that machine type)
* Overhaul of threading model, many thread safety improvements.
* Asynchronous rendering.
* Memory and object lifetime fixes.
* Refactoring to split generic and (vmapple) MMIO variant specific
code.
Implementation wise, most of the complexity lies in the differing threading
models of ParavirtualizedGraphics.framework, which uses libdispatch and
internal locks, versus QEMU, which heavily uses the BQL, especially during
memory-mapped device I/O. Great care has therefore been taken to prevent
deadlocks by never calling into PVG methods while holding the BQL, and
similarly never acquiring the BQL in a callback from PVG. Different strategies
have been used (libdispatch, blocking and non-blocking BHs, RCU, etc.)
depending on the specific requirements at each framework entry and exit point.
Signed-off-by: Phil Dennis-Jordan <phil@philjordan.eu>
---
v2:
* Cherry-pick/rebase conflict fixes
* BQL function renaming
* Moved from hw/vmapple/ (useful outside that machine type)
* Code review comments: Switched to DEFINE_TYPES macro & little endian
MMIO.
* Removed some dead/superfluous code
* Mad set_mode thread & memory safe
* Added migration blocker due to lack of (de-)serialisation.
* Fixes to ObjC refcounting and autorelease pool usage.
* Fixed ObjC new/init misuse
* Switched to ObjC category extension for private property.
* Simplified task memory mapping and made it thread safe.
* Refactoring to split generic and vmapple MMIO variant specific
code.
* Switched to asynchronous MMIO writes on x86-64
* Rendering and graphics update are now done asynchronously
* Fixed cursor handling
* Coding convention fixes
* Removed software cursor compositing
v3:
* Rebased on latest upstream, fixed breakages including switching to
Resettable methods.
* Squashed patches dealing with dGPUs, MMIO area size, and GPU picking.
* Allow re-entrant MMIO; this simplifies the code and solves the divergence
between x86-64 and arm64 variants.
v4:
* Renamed '-vmapple' device variant to '-mmio'
* MMIO device type now requires aarch64 host and guest
* Complete overhaul of the glue code for making Qemu's and
ParavirtualizedGraphics.framework's threading and synchronisation models
work together. Calls into PVG are from dispatch queues while the
BQL-holding initiating thread processes AIO context events; callbacks from
PVG are scheduled as BHs on the BQL/main AIO context, awaiting completion
where necessary.
* Guest frame rendering state is covered by the BQL, with only the PVG calls
outside the lock, and serialised on the named render_queue.
* Simplified logic for dropping frames in-flight during mode changes, fixed
bug in pending frames logic.
* Addressed smaller code review notes such as: function naming, object type
declarations, type names/declarations/casts, code formatting, #include
order, over-cautious ObjC retain/release, what goes in init vs realize,
etc.
v5:
* Smaller non-functional fixes in response to review comments, such as using
NULL for the AIO_WAIT_WHILE context argument, type name formatting,
deleting leftover debug code, logging improvements, state struct field
order and documentation improvements, etc.
* Instead of a single condition variable for all synchronous BH job types,
there is now one for each callback block. This reduces the number
of threads being awoken unnecessarily to near zero.
* MMIO device variant: Unified the BH job for raising interrupts.
* Use DMA APIs for PVG framework's guest memory read requests.
* Thread safety improvements: ensure mutable AppleGFXState fields are not
accessed outside the appropriate lock. Added dedicated mutex for the task
list.
* Retain references to MemoryRegions for which there exist mappings in each
PGTask, and for IOSurface mappings.
v6:
* Switched PGTask_s's' mapped_regions from GPtrArray to GArray
* Allow DisplaySurface to manage its own vram now that texture -> vram copy
occurs under BQL.
* Memory mapping operations now use RCU_READ_LOCK_GUARD() where possible
instead of a heavy-weight BH job to acquire the BQL.
* Changed PVG cursor and mode setting callbacks to kick off BHs instead of
libdispatch tasks which then locked the BQL explicitly.
* The single remaining callback which must wait for a BH to complete now
creates an ephemeral QemuSemaphore to await completion.
* Re-removed tracking of mapped surface manager memory regions. Just look up
and ref/unref the memory regions in the map/unmap callbacks.
* Re-ordered functions in apple-gfx.m to group them by area of functionality.
* Improved comments and tweaked some names.
v7:
* Use g_ptr_array_find() helper function
* Error handling coding style tweak
v8:
* Renamed apple_gfx_host_address_for_gpa_range() to
apple_gfx_host_ptr_for_gpa_range(), and made it return a void* instead of
uintptr_t. Fixed up callers and related code.
* Some adjustments to types used.
* Variable naming tweaks for better clarity.
* Fixed leak in unlikely realize error case.
* Fixed typo in unmap call.
* Don't bother with dummy argument for g_ptr_array_find(), NULL works too.
hw/display/Kconfig | 9 +
hw/display/apple-gfx-mmio.m | 282 +++++++++++++
hw/display/apple-gfx.h | 65 +++
hw/display/apple-gfx.m | 769 ++++++++++++++++++++++++++++++++++++
hw/display/meson.build | 4 +
hw/display/trace-events | 28 ++
meson.build | 4 +
7 files changed, 1161 insertions(+)
create mode 100644 hw/display/apple-gfx-mmio.m
create mode 100644 hw/display/apple-gfx.h
create mode 100644 hw/display/apple-gfx.m
diff --git a/hw/display/Kconfig b/hw/display/Kconfig
index 2250c740078..6a9b7b19ada 100644
--- a/hw/display/Kconfig
+++ b/hw/display/Kconfig
@@ -140,3 +140,12 @@ config XLNX_DISPLAYPORT
config DM163
bool
+
+config MAC_PVG
+ bool
+ default y
+
+config MAC_PVG_MMIO
+ bool
+ depends on MAC_PVG && AARCH64
+
diff --git a/hw/display/apple-gfx-mmio.m b/hw/display/apple-gfx-mmio.m
new file mode 100644
index 00000000000..2c5f426886c
--- /dev/null
+++ b/hw/display/apple-gfx-mmio.m
@@ -0,0 +1,282 @@
+/*
+ * QEMU Apple ParavirtualizedGraphics.framework device, MMIO (arm64) variant
+ *
+ * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * ParavirtualizedGraphics.framework is a set of libraries that macOS provides
+ * which implements 3d graphics passthrough to the host as well as a
+ * proprietary guest communication channel to drive it. This device model
+ * implements support to drive that library from within QEMU as an MMIO-based
+ * system device for macOS on arm64 VMs.
+ */
+
+#include "qemu/osdep.h"
+#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h>
+#include "apple-gfx.h"
+#include "monitor/monitor.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "trace.h"
+#include "qemu/log.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(AppleGFXMMIOState, APPLE_GFX_MMIO)
+
+/*
+ * ParavirtualizedGraphics.Framework only ships header files for the PCI
+ * variant which does not include IOSFC descriptors and host devices. We add
+ * their definitions here so that we can also work with the ARM version.
+ */
+typedef bool(^IOSFCRaiseInterrupt)(uint32_t vector);
+typedef bool(^IOSFCUnmapMemory)(
+ void *, void *, void *, void *, void *, void *);
+typedef bool(^IOSFCMapMemory)(
+ uint64_t phys, uint64_t len, bool ro, void **va, void *, void *);
+
+@interface PGDeviceDescriptor (IOSurfaceMapper)
+@property (readwrite, nonatomic) bool usingIOSurfaceMapper;
+@end
+
+@interface PGIOSurfaceHostDeviceDescriptor : NSObject
+-(PGIOSurfaceHostDeviceDescriptor *)init;
+@property (readwrite, nonatomic, copy, nullable) IOSFCMapMemory mapMemory;
+@property (readwrite, nonatomic, copy, nullable) IOSFCUnmapMemory unmapMemory;
+@property (readwrite, nonatomic, copy, nullable) IOSFCRaiseInterrupt
raiseInterrupt;
+@end
+
+@interface PGIOSurfaceHostDevice : NSObject
+-(instancetype)initWithDescriptor:(PGIOSurfaceHostDeviceDescriptor *)desc;
+-(uint32_t)mmioReadAtOffset:(size_t)offset;
+-(void)mmioWriteAtOffset:(size_t)offset value:(uint32_t)value;
+@end
+
+struct AppleGFXMapSurfaceMemoryJob;
+struct AppleGFXMMIOState {
+ SysBusDevice parent_obj;
+
+ AppleGFXState common;
+
+ qemu_irq irq_gfx;
+ qemu_irq irq_iosfc;
+ MemoryRegion iomem_iosfc;
+ PGIOSurfaceHostDevice *pgiosfc;
+};
+
+typedef struct AppleGFXMMIOJob {
+ AppleGFXMMIOState *state;
+ uint64_t offset;
+ uint64_t value;
+ bool completed;
+} AppleGFXMMIOJob;
+
+static void iosfc_do_read(void *opaque)
+{
+ AppleGFXMMIOJob *job = opaque;
+ job->value = [job->state->pgiosfc mmioReadAtOffset:job->offset];
+ qatomic_set(&job->completed, true);
+ aio_wait_kick();
+}
+
+static uint64_t iosfc_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AppleGFXMMIOJob job = {
+ .state = opaque,
+ .offset = offset,
+ .completed = false,
+ };
+ dispatch_queue_t queue =
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+ dispatch_async_f(queue, &job, iosfc_do_read);
+ AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
+
+ trace_apple_gfx_mmio_iosfc_read(offset, job.value);
+ return job.value;
+}
+
+static void iosfc_do_write(void *opaque)
+{
+ AppleGFXMMIOJob *job = opaque;
+ [job->state->pgiosfc mmioWriteAtOffset:job->offset value:job->value];
+ qatomic_set(&job->completed, true);
+ aio_wait_kick();
+}
+
+static void iosfc_write(void *opaque, hwaddr offset, uint64_t val,
+ unsigned size)
+{
+ AppleGFXMMIOJob job = {
+ .state = opaque,
+ .offset = offset,
+ .value = val,
+ .completed = false,
+ };
+ dispatch_queue_t queue =
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+ dispatch_async_f(queue, &job, iosfc_do_write);
+ AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
+
+ trace_apple_gfx_mmio_iosfc_write(offset, val);
+}
+
+static const MemoryRegionOps apple_iosfc_ops = {
+ .read = iosfc_read,
+ .write = iosfc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static void raise_irq_bh(void *opaque)
+{
+ qemu_irq *irq = opaque;
+
+ qemu_irq_pulse(*irq);
+}
+
+static void *apple_gfx_mmio_map_surface_memory(uint64_t guest_physical_address,
+ uint64_t length, bool read_only)
+{
+ void *mem;
+ MemoryRegion *region = NULL;
+
+ RCU_READ_LOCK_GUARD();
+ mem = apple_gfx_host_ptr_for_gpa_range(guest_physical_address,
+ length, read_only, ®ion);
+ if (mem) {
+ memory_region_ref(region);
+ }
+ return mem;
+}
+
+static bool apple_gfx_mmio_unmap_surface_memory(void *ptr)
+{
+ MemoryRegion *region;
+ ram_addr_t offset = 0;
+
+ RCU_READ_LOCK_GUARD();
+ region = memory_region_from_host(ptr, &offset);
+ if (!region) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: memory at %p to be unmapped not "
+ "found.\n",
+ __func__, ptr);
+ return false;
+ }
+
+ trace_apple_gfx_iosfc_unmap_memory_region(ptr, region);
+ memory_region_unref(region);
+ return true;
+}
+
+static PGIOSurfaceHostDevice *apple_gfx_prepare_iosurface_host_device(
+ AppleGFXMMIOState *s)
+{
+ PGIOSurfaceHostDeviceDescriptor *iosfc_desc =
+ [PGIOSurfaceHostDeviceDescriptor new];
+ PGIOSurfaceHostDevice *iosfc_host_dev = nil;
+
+ iosfc_desc.mapMemory =
+ ^bool(uint64_t phys, uint64_t len, bool ro, void **va, void *e, void
*f) {
+ *va = apple_gfx_mmio_map_surface_memory(phys, len, ro);
+
+ trace_apple_gfx_iosfc_map_memory(phys, len, ro, va, e, f, *va);
+
+ return *va != NULL;
+ };
+
+ iosfc_desc.unmapMemory =
+ ^bool(void *va, void *b, void *c, void *d, void *e, void *f) {
+ return apple_gfx_mmio_unmap_surface_memory(va);
+ };
+
+ iosfc_desc.raiseInterrupt = ^bool(uint32_t vector) {
+ trace_apple_gfx_iosfc_raise_irq(vector);
+ aio_bh_schedule_oneshot(qemu_get_aio_context(),
+ raise_irq_bh, &s->irq_iosfc);
+ return true;
+ };
+
+ iosfc_host_dev =
+ [[PGIOSurfaceHostDevice alloc] initWithDescriptor:iosfc_desc];
+ [iosfc_desc release];
+ return iosfc_host_dev;
+}
+
+static void apple_gfx_mmio_realize(DeviceState *dev, Error **errp)
+{
+ @autoreleasepool {
+ AppleGFXMMIOState *s = APPLE_GFX_MMIO(dev);
+ PGDeviceDescriptor *desc = [PGDeviceDescriptor new];
+
+ desc.raiseInterrupt = ^(uint32_t vector) {
+ trace_apple_gfx_raise_irq(vector);
+ aio_bh_schedule_oneshot(qemu_get_aio_context(),
+ raise_irq_bh, &s->irq_gfx);
+ };
+
+ desc.usingIOSurfaceMapper = true;
+ s->pgiosfc = apple_gfx_prepare_iosurface_host_device(s);
+
+ apple_gfx_common_realize(&s->common, desc, errp);
+ if (*errp) {
+ [s->pgiosfc release];
+ s->pgiosfc = nil;
+ }
+
+ [desc release];
+ desc = nil;
+ }
+}
+
+static void apple_gfx_mmio_init(Object *obj)
+{
+ AppleGFXMMIOState *s = APPLE_GFX_MMIO(obj);
+
+ apple_gfx_common_init(obj, &s->common, TYPE_APPLE_GFX_MMIO);
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->common.iomem_gfx);
+ memory_region_init_io(&s->iomem_iosfc, obj, &apple_iosfc_ops, s,
+ TYPE_APPLE_GFX_MMIO, 0x10000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem_iosfc);
+ sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq_gfx);
+ sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq_iosfc);
+}
+
+static void apple_gfx_mmio_reset(Object *obj, ResetType type)
+{
+ AppleGFXMMIOState *s = APPLE_GFX_MMIO(obj);
+ [s->common.pgdev reset];
+}
+
+
+static void apple_gfx_mmio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.hold = apple_gfx_mmio_reset;
+ dc->hotpluggable = false;
+ dc->realize = apple_gfx_mmio_realize;
+}
+
+static TypeInfo apple_gfx_mmio_types[] = {
+ {
+ .name = TYPE_APPLE_GFX_MMIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AppleGFXMMIOState),
+ .class_init = apple_gfx_mmio_class_init,
+ .instance_init = apple_gfx_mmio_init,
+ }
+};
+DEFINE_TYPES(apple_gfx_mmio_types)
diff --git a/hw/display/apple-gfx.h b/hw/display/apple-gfx.h
new file mode 100644
index 00000000000..14ac2af8fc3
--- /dev/null
+++ b/hw/display/apple-gfx.h
@@ -0,0 +1,65 @@
+/*
+ * Data structures and functions shared between variants of the macOS
+ * ParavirtualizedGraphics.framework based apple-gfx display adapter.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QEMU_APPLE_GFX_H
+#define QEMU_APPLE_GFX_H
+
+#define TYPE_APPLE_GFX_MMIO "apple-gfx-mmio"
+#define TYPE_APPLE_GFX_PCI "apple-gfx-pci"
+
+#include "qemu/osdep.h"
+#include <dispatch/dispatch.h>
+#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h>
+#include "qemu/typedefs.h"
+#include "exec/memory.h"
+#include "ui/surface.h"
+
+@class PGDeviceDescriptor;
+@protocol PGDevice;
+@protocol PGDisplay;
+@protocol MTLDevice;
+@protocol MTLTexture;
+@protocol MTLCommandQueue;
+
+typedef QTAILQ_HEAD(, PGTask_s) PGTaskList;
+
+typedef struct AppleGFXState {
+ /* Initialised on init/realize() */
+ MemoryRegion iomem_gfx;
+ id<PGDevice> pgdev;
+ id<PGDisplay> pgdisp;
+ QemuConsole *con;
+ id<MTLDevice> mtl;
+ id<MTLCommandQueue> mtl_queue;
+ dispatch_queue_t render_queue;
+
+ /* List `tasks` is protected by task_mutex */
+ QemuMutex task_mutex;
+ PGTaskList tasks;
+
+ /* Mutable state (BQL protected) */
+ QEMUCursor *cursor;
+ DisplaySurface *surface;
+ id<MTLTexture> texture;
+ int8_t pending_frames; /* # guest frames in the rendering pipeline */
+ bool gfx_update_requested; /* QEMU display system wants a new frame */
+ bool new_frame_ready; /* Guest has rendered a frame, ready to be used */
+ bool using_managed_texture_storage;
+
+ /* Mutable state (atomic) */
+ bool cursor_show;
+} AppleGFXState;
+
+void apple_gfx_common_init(Object *obj, AppleGFXState *s, const char*
obj_name);
+void apple_gfx_common_realize(AppleGFXState *s, PGDeviceDescriptor *desc,
+ Error **errp);
+void *apple_gfx_host_ptr_for_gpa_range(uint64_t guest_physical,
+ uint64_t length, bool read_only,
+ MemoryRegion **mapping_in_region);
+
+#endif
+
diff --git a/hw/display/apple-gfx.m b/hw/display/apple-gfx.m
new file mode 100644
index 00000000000..913937b5255
--- /dev/null
+++ b/hw/display/apple-gfx.m
@@ -0,0 +1,769 @@
+/*
+ * QEMU Apple ParavirtualizedGraphics.framework device
+ *
+ * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * ParavirtualizedGraphics.framework is a set of libraries that macOS provides
+ * which implements 3d graphics passthrough to the host as well as a
+ * proprietary guest communication channel to drive it. This device model
+ * implements support to drive that library from within QEMU.
+ */
+
+#include "qemu/osdep.h"
+#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h>
+#include <mach/mach_vm.h>
+#include "apple-gfx.h"
+#include "trace.h"
+#include "qemu-main.h"
+#include "exec/address-spaces.h"
+#include "migration/blocker.h"
+#include "monitor/monitor.h"
+#include "qemu/main-loop.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qapi/visitor.h"
+#include "qapi/error.h"
+#include "sysemu/dma.h"
+#include "ui/console.h"
+
+static const PGDisplayCoord_t apple_gfx_modes[] = {
+ { .x = 1440, .y = 1080 },
+ { .x = 1280, .y = 1024 },
+};
+
+/* ------ PGTask and task operations: new/destroy/map/unmap ------ */
+
+/*
+ * This implements the type declared in <ParavirtualizedGraphics/PGDevice.h>
+ * which is opaque from the framework's point of view. It is used in callbacks
+ * in the form of its typedef PGTask_t, which also already exists in the
+ * framework headers.
+ *
+ * A "task" in PVG terminology represents a host-virtual contiguous address
+ * range which is reserved in a large chunk on task creation. The mapMemory
+ * callback then requests ranges of guest system memory (identified by their
+ * GPA) to be mapped into subranges of this reserved address space.
+ * This type of operation isn't well-supported by QEMU's memory subsystem,
+ * but it is fortunately trivial to achieve with Darwin's mach_vm_remap() call,
+ * which allows us to refer to the same backing memory via multiple virtual
+ * address ranges. The Mach VM APIs are therefore used throughout for managing
+ * task memory.
+ */
+struct PGTask_s {
+ QTAILQ_ENTRY(PGTask_s) node;
+ AppleGFXState *s;
+ mach_vm_address_t address;
+ uint64_t len;
+ /*
+ * All unique MemoryRegions for which a mapping has been created in in this
+ * task, and on which we have thus called memory_region_ref(). There are
+ * usually very few regions of system RAM in total, so we expect this array
+ * to be very short. Therefore, no need for sorting or fancy search
+ * algorithms, linear search will do.
+ * Protected by AppleGFXState's task_mutex.
+ */
+ GPtrArray *mapped_regions;
+};
+
+static Error *apple_gfx_mig_blocker;
+
+static PGTask_t *apple_gfx_new_task(AppleGFXState *s, uint64_t len)
+{
+ mach_vm_address_t task_mem;
+ PGTask_t *task;
+ kern_return_t r;
+
+ r = mach_vm_allocate(mach_task_self(), &task_mem, len, VM_FLAGS_ANYWHERE);
+ if (r != KERN_SUCCESS) {
+ return NULL;
+ }
+
+ task = g_new0(PGTask_t, 1);
+ task->s = s;
+ task->address = task_mem;
+ task->len = len;
+ task->mapped_regions = g_ptr_array_sized_new(2 /* Usually enough */);
+
+ QEMU_LOCK_GUARD(&s->task_mutex);
+ QTAILQ_INSERT_TAIL(&s->tasks, task, node);
+
+ return task;
+}
+
+static void apple_gfx_destroy_task(AppleGFXState *s, PGTask_t *task)
+{
+ GPtrArray *regions = task->mapped_regions;
+ MemoryRegion *region;
+ size_t i;
+
+ for (i = 0; i < regions->len; ++i) {
+ region = g_ptr_array_index(regions, i);
+ memory_region_unref(region);
+ }
+ g_ptr_array_unref(regions);
+
+ mach_vm_deallocate(mach_task_self(), task->address, task->len);
+
+ QEMU_LOCK_GUARD(&s->task_mutex);
+ QTAILQ_REMOVE(&s->tasks, task, node);
+ g_free(task);
+}
+
+void *apple_gfx_host_ptr_for_gpa_range(uint64_t guest_physical,
+ uint64_t length, bool read_only,
+ MemoryRegion **mapping_in_region)
+{
+ MemoryRegion *ram_region;
+ char *host_ptr;
+ hwaddr ram_region_offset = 0;
+ hwaddr ram_region_length = length;
+
+ ram_region = address_space_translate(&address_space_memory,
+ guest_physical,
+ &ram_region_offset,
+ &ram_region_length, !read_only,
+ MEMTXATTRS_UNSPECIFIED);
+
+ if (!ram_region || ram_region_length < length ||
+ !memory_access_is_direct(ram_region, !read_only)) {
+ return NULL;
+ }
+
+ host_ptr = memory_region_get_ram_ptr(ram_region);
+ if (!host_ptr) {
+ return NULL;
+ }
+ host_ptr += ram_region_offset;
+ *mapping_in_region = ram_region;
+ return host_ptr;
+}
+
+/* Returns false if the region is already in the array */
+static bool add_new_region(GPtrArray *regions, MemoryRegion *region)
+{
+ if (g_ptr_array_find(regions, region, NULL)) {
+ return false;
+ }
+
+ g_ptr_array_add(regions, region);
+ return true;
+}
+
+static bool apple_gfx_task_map_memory(AppleGFXState *s, PGTask_t *task,
+ uint64_t virtual_offset,
+ PGPhysicalMemoryRange_t *ranges,
+ uint32_t range_count, bool read_only)
+{
+ kern_return_t r;
+ void *source_ptr;
+ mach_vm_address_t target;
+ vm_prot_t cur_protection, max_protection;
+ bool success = true;
+ MemoryRegion *region;
+
+ RCU_READ_LOCK_GUARD();
+ QEMU_LOCK_GUARD(&s->task_mutex);
+
+ trace_apple_gfx_map_memory(task, range_count, virtual_offset, read_only);
+ for (int i = 0; i < range_count; i++) {
+ PGPhysicalMemoryRange_t *range = &ranges[i];
+
+ target = task->address + virtual_offset;
+ virtual_offset += range->physicalLength;
+
+ trace_apple_gfx_map_memory_range(i, range->physicalAddress,
+ range->physicalLength);
+
+ region = NULL;
+ source_ptr = apple_gfx_host_ptr_for_gpa_range(range->physicalAddress,
+ range->physicalLength,
+ read_only, ®ion);
+ if (!source_ptr) {
+ success = false;
+ continue;
+ }
+
+ if (add_new_region(task->mapped_regions, region)) {
+ memory_region_ref(region);
+ }
+
+ cur_protection = 0;
+ max_protection = 0;
+ /* Map guest RAM at range->physicalAddress into PG task memory range */
+ r = mach_vm_remap(mach_task_self(),
+ &target, range->physicalLength, vm_page_size - 1,
+ VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
+ mach_task_self(), (mach_vm_address_t)source_ptr,
+ false /* shared mapping, no copy */,
+ &cur_protection, &max_protection,
+ VM_INHERIT_COPY);
+ trace_apple_gfx_remap(r, source_ptr, target);
+ g_assert(r == KERN_SUCCESS);
+ }
+
+ return success;
+}
+
+static void apple_gfx_task_unmap_memory(AppleGFXState *s, PGTask_t *task,
+ uint64_t virtual_offset, uint64_t
length)
+{
+ kern_return_t r;
+ mach_vm_address_t range_address;
+
+ trace_apple_gfx_unmap_memory(task, virtual_offset, length);
+
+ /*
+ * Replace task memory range with fresh 0 pages, undoing the mapping
+ * from guest RAM.
+ */
+ range_address = task->address + virtual_offset;
+ r = mach_vm_allocate(mach_task_self(), &range_address, length,
+ VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE);
+ g_assert(r == KERN_SUCCESS);
+}
+
+/* ------ Rendering and frame management ------ */
+
+static void apple_gfx_render_frame_completed(AppleGFXState *s,
+ uint32_t width, uint32_t height);
+
+static void apple_gfx_render_new_frame_bql_unlock(AppleGFXState *s)
+{
+ BOOL r;
+ bool managed_texture = s->using_managed_texture_storage;
+ uint32_t width = surface_width(s->surface);
+ uint32_t height = surface_height(s->surface);
+ MTLRegion region = MTLRegionMake2D(0, 0, width, height);
+ id<MTLCommandBuffer> command_buffer = [s->mtl_queue commandBuffer];
+ id<MTLTexture> texture = s->texture;
+
+ assert(bql_locked());
+ [texture retain];
+
+ bql_unlock();