| /* |
| * QEMU Apple ParavirtualizedGraphics.framework device, MMIO (arm64) variant |
| * |
| * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| * |
| * 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" |
| #include "qemu/log.h" |
| #include "block/aio-wait.h" |
| #include "hw/sysbus.h" |
| #include "hw/irq.h" |
| #include "apple-gfx.h" |
| #include "trace.h" |
| |
| #import <ParavirtualizedGraphics/ParavirtualizedGraphics.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; |
| |
| 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); |
| |
| if (!apple_gfx_common_realize(&s->common, dev, desc, 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 const Property apple_gfx_mmio_properties[] = { |
| DEFINE_PROP_ARRAY("display-modes", AppleGFXMMIOState, |
| common.num_display_modes, common.display_modes, |
| qdev_prop_apple_gfx_display_mode, AppleGFXDisplayMode), |
| }; |
| |
| static void apple_gfx_mmio_class_init(ObjectClass *klass, const 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; |
| |
| device_class_set_props(dc, apple_gfx_mmio_properties); |
| } |
| |
| static const 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) |