xref: /qemu/hw/display/apple-gfx-mmio.m (revision 70ce076fa6dff60585c229a4b641b13e64bf03cf)
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, &region);
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