1/* 2 * QEMU Apple ParavirtualizedGraphics.framework device, MMIO (arm64) variant 3 * 4 * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 * 6 * SPDX-License-Identifier: GPL-2.0-or-later 7 * 8 * ParavirtualizedGraphics.framework is a set of libraries that macOS provides 9 * which implements 3d graphics passthrough to the host as well as a 10 * proprietary guest communication channel to drive it. This device model 11 * implements support to drive that library from within QEMU as an MMIO-based 12 * system device for macOS on arm64 VMs. 13 */ 14 15#include "qemu/osdep.h" 16#include "qemu/log.h" 17#include "block/aio-wait.h" 18#include "hw/sysbus.h" 19#include "hw/irq.h" 20#include "apple-gfx.h" 21#include "trace.h" 22 23#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h> 24 25OBJECT_DECLARE_SIMPLE_TYPE(AppleGFXMMIOState, APPLE_GFX_MMIO) 26 27/* 28 * ParavirtualizedGraphics.Framework only ships header files for the PCI 29 * variant which does not include IOSFC descriptors and host devices. We add 30 * their definitions here so that we can also work with the ARM version. 31 */ 32typedef bool(^IOSFCRaiseInterrupt)(uint32_t vector); 33typedef bool(^IOSFCUnmapMemory)(void *, void *, void *, void *, void *, void *); 34typedef bool(^IOSFCMapMemory)(uint64_t phys, uint64_t len, bool ro, void **va, 35 void *, void *); 36 37@interface PGDeviceDescriptor (IOSurfaceMapper) 38@property (readwrite, nonatomic) bool usingIOSurfaceMapper; 39@end 40 41@interface PGIOSurfaceHostDeviceDescriptor : NSObject 42-(PGIOSurfaceHostDeviceDescriptor *)init; 43@property (readwrite, nonatomic, copy, nullable) IOSFCMapMemory mapMemory; 44@property (readwrite, nonatomic, copy, nullable) IOSFCUnmapMemory unmapMemory; 45@property (readwrite, nonatomic, copy, nullable) IOSFCRaiseInterrupt raiseInterrupt; 46@end 47 48@interface PGIOSurfaceHostDevice : NSObject 49-(instancetype)initWithDescriptor:(PGIOSurfaceHostDeviceDescriptor *)desc; 50-(uint32_t)mmioReadAtOffset:(size_t)offset; 51-(void)mmioWriteAtOffset:(size_t)offset value:(uint32_t)value; 52@end 53 54struct AppleGFXMapSurfaceMemoryJob; 55struct AppleGFXMMIOState { 56 SysBusDevice parent_obj; 57 58 AppleGFXState common; 59 60 qemu_irq irq_gfx; 61 qemu_irq irq_iosfc; 62 MemoryRegion iomem_iosfc; 63 PGIOSurfaceHostDevice *pgiosfc; 64}; 65 66typedef struct AppleGFXMMIOJob { 67 AppleGFXMMIOState *state; 68 uint64_t offset; 69 uint64_t value; 70 bool completed; 71} AppleGFXMMIOJob; 72 73static void iosfc_do_read(void *opaque) 74{ 75 AppleGFXMMIOJob *job = opaque; 76 job->value = [job->state->pgiosfc mmioReadAtOffset:job->offset]; 77 qatomic_set(&job->completed, true); 78 aio_wait_kick(); 79} 80 81static uint64_t iosfc_read(void *opaque, hwaddr offset, unsigned size) 82{ 83 AppleGFXMMIOJob job = { 84 .state = opaque, 85 .offset = offset, 86 .completed = false, 87 }; 88 dispatch_queue_t queue = 89 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 90 91 dispatch_async_f(queue, &job, iosfc_do_read); 92 AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed)); 93 94 trace_apple_gfx_mmio_iosfc_read(offset, job.value); 95 return job.value; 96} 97 98static void iosfc_do_write(void *opaque) 99{ 100 AppleGFXMMIOJob *job = opaque; 101 [job->state->pgiosfc mmioWriteAtOffset:job->offset value:job->value]; 102 qatomic_set(&job->completed, true); 103 aio_wait_kick(); 104} 105 106static void iosfc_write(void *opaque, hwaddr offset, uint64_t val, 107 unsigned size) 108{ 109 AppleGFXMMIOJob job = { 110 .state = opaque, 111 .offset = offset, 112 .value = val, 113 .completed = false, 114 }; 115 dispatch_queue_t queue = 116 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 117 118 dispatch_async_f(queue, &job, iosfc_do_write); 119 AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed)); 120 121 trace_apple_gfx_mmio_iosfc_write(offset, val); 122} 123 124static const MemoryRegionOps apple_iosfc_ops = { 125 .read = iosfc_read, 126 .write = iosfc_write, 127 .endianness = DEVICE_LITTLE_ENDIAN, 128 .valid = { 129 .min_access_size = 4, 130 .max_access_size = 8, 131 }, 132 .impl = { 133 .min_access_size = 4, 134 .max_access_size = 8, 135 }, 136}; 137 138static void raise_irq_bh(void *opaque) 139{ 140 qemu_irq *irq = opaque; 141 142 qemu_irq_pulse(*irq); 143} 144 145static void *apple_gfx_mmio_map_surface_memory(uint64_t guest_physical_address, 146 uint64_t length, bool read_only) 147{ 148 void *mem; 149 MemoryRegion *region = NULL; 150 151 RCU_READ_LOCK_GUARD(); 152 mem = apple_gfx_host_ptr_for_gpa_range(guest_physical_address, 153 length, read_only, ®ion); 154 if (mem) { 155 memory_region_ref(region); 156 } 157 return mem; 158} 159 160static bool apple_gfx_mmio_unmap_surface_memory(void *ptr) 161{ 162 MemoryRegion *region; 163 ram_addr_t offset = 0; 164 165 RCU_READ_LOCK_GUARD(); 166 region = memory_region_from_host(ptr, &offset); 167 if (!region) { 168 qemu_log_mask(LOG_GUEST_ERROR, 169 "%s: memory at %p to be unmapped not found.\n", 170 __func__, ptr); 171 return false; 172 } 173 174 trace_apple_gfx_iosfc_unmap_memory_region(ptr, region); 175 memory_region_unref(region); 176 return true; 177} 178 179static PGIOSurfaceHostDevice *apple_gfx_prepare_iosurface_host_device( 180 AppleGFXMMIOState *s) 181{ 182 PGIOSurfaceHostDeviceDescriptor *iosfc_desc = 183 [PGIOSurfaceHostDeviceDescriptor new]; 184 PGIOSurfaceHostDevice *iosfc_host_dev; 185 186 iosfc_desc.mapMemory = 187 ^bool(uint64_t phys, uint64_t len, bool ro, void **va, void *e, void *f) { 188 *va = apple_gfx_mmio_map_surface_memory(phys, len, ro); 189 190 trace_apple_gfx_iosfc_map_memory(phys, len, ro, va, e, f, *va); 191 192 return *va != NULL; 193 }; 194 195 iosfc_desc.unmapMemory = 196 ^bool(void *va, void *b, void *c, void *d, void *e, void *f) { 197 return apple_gfx_mmio_unmap_surface_memory(va); 198 }; 199 200 iosfc_desc.raiseInterrupt = ^bool(uint32_t vector) { 201 trace_apple_gfx_iosfc_raise_irq(vector); 202 aio_bh_schedule_oneshot(qemu_get_aio_context(), 203 raise_irq_bh, &s->irq_iosfc); 204 return true; 205 }; 206 207 iosfc_host_dev = 208 [[PGIOSurfaceHostDevice alloc] initWithDescriptor:iosfc_desc]; 209 [iosfc_desc release]; 210 return iosfc_host_dev; 211} 212 213static void apple_gfx_mmio_realize(DeviceState *dev, Error **errp) 214{ 215 @autoreleasepool { 216 AppleGFXMMIOState *s = APPLE_GFX_MMIO(dev); 217 PGDeviceDescriptor *desc = [PGDeviceDescriptor new]; 218 219 desc.raiseInterrupt = ^(uint32_t vector) { 220 trace_apple_gfx_raise_irq(vector); 221 aio_bh_schedule_oneshot(qemu_get_aio_context(), 222 raise_irq_bh, &s->irq_gfx); 223 }; 224 225 desc.usingIOSurfaceMapper = true; 226 s->pgiosfc = apple_gfx_prepare_iosurface_host_device(s); 227 228 if (!apple_gfx_common_realize(&s->common, dev, desc, errp)) { 229 [s->pgiosfc release]; 230 s->pgiosfc = nil; 231 } 232 233 [desc release]; 234 desc = nil; 235 } 236} 237 238static void apple_gfx_mmio_init(Object *obj) 239{ 240 AppleGFXMMIOState *s = APPLE_GFX_MMIO(obj); 241 242 apple_gfx_common_init(obj, &s->common, TYPE_APPLE_GFX_MMIO); 243 244 sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->common.iomem_gfx); 245 memory_region_init_io(&s->iomem_iosfc, obj, &apple_iosfc_ops, s, 246 TYPE_APPLE_GFX_MMIO, 0x10000); 247 sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem_iosfc); 248 sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq_gfx); 249 sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq_iosfc); 250} 251 252static void apple_gfx_mmio_reset(Object *obj, ResetType type) 253{ 254 AppleGFXMMIOState *s = APPLE_GFX_MMIO(obj); 255 [s->common.pgdev reset]; 256} 257 258static const Property apple_gfx_mmio_properties[] = { 259 DEFINE_PROP_ARRAY("display-modes", AppleGFXMMIOState, 260 common.num_display_modes, common.display_modes, 261 qdev_prop_apple_gfx_display_mode, AppleGFXDisplayMode), 262}; 263 264static void apple_gfx_mmio_class_init(ObjectClass *klass, void *data) 265{ 266 DeviceClass *dc = DEVICE_CLASS(klass); 267 ResettableClass *rc = RESETTABLE_CLASS(klass); 268 269 rc->phases.hold = apple_gfx_mmio_reset; 270 dc->hotpluggable = false; 271 dc->realize = apple_gfx_mmio_realize; 272 273 device_class_set_props(dc, apple_gfx_mmio_properties); 274} 275 276static const TypeInfo apple_gfx_mmio_types[] = { 277 { 278 .name = TYPE_APPLE_GFX_MMIO, 279 .parent = TYPE_SYS_BUS_DEVICE, 280 .instance_size = sizeof(AppleGFXMMIOState), 281 .class_init = apple_gfx_mmio_class_init, 282 .instance_init = apple_gfx_mmio_init, 283 } 284}; 285DEFINE_TYPES(apple_gfx_mmio_types) 286