xref: /qemu/hw/display/apple-gfx.m (revision bb43a2342ddf57e6cd4f24161ee4e641cfbcceca)
12352159cSPhil Dennis-Jordan/*
22352159cSPhil Dennis-Jordan * QEMU Apple ParavirtualizedGraphics.framework device
32352159cSPhil Dennis-Jordan *
42352159cSPhil Dennis-Jordan * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
52352159cSPhil Dennis-Jordan *
62352159cSPhil Dennis-Jordan * SPDX-License-Identifier: GPL-2.0-or-later
72352159cSPhil Dennis-Jordan *
82352159cSPhil Dennis-Jordan * ParavirtualizedGraphics.framework is a set of libraries that macOS provides
92352159cSPhil Dennis-Jordan * which implements 3d graphics passthrough to the host as well as a
102352159cSPhil Dennis-Jordan * proprietary guest communication channel to drive it. This device model
112352159cSPhil Dennis-Jordan * implements support to drive that library from within QEMU.
122352159cSPhil Dennis-Jordan */
132352159cSPhil Dennis-Jordan
142352159cSPhil Dennis-Jordan#include "qemu/osdep.h"
152352159cSPhil Dennis-Jordan#include "qemu/lockable.h"
162352159cSPhil Dennis-Jordan#include "qemu/cutils.h"
172352159cSPhil Dennis-Jordan#include "qemu/log.h"
182352159cSPhil Dennis-Jordan#include "qapi/visitor.h"
192352159cSPhil Dennis-Jordan#include "qapi/error.h"
202352159cSPhil Dennis-Jordan#include "block/aio-wait.h"
212352159cSPhil Dennis-Jordan#include "exec/address-spaces.h"
222352159cSPhil Dennis-Jordan#include "system/dma.h"
232352159cSPhil Dennis-Jordan#include "migration/blocker.h"
242352159cSPhil Dennis-Jordan#include "ui/console.h"
252352159cSPhil Dennis-Jordan#include "apple-gfx.h"
262352159cSPhil Dennis-Jordan#include "trace.h"
272352159cSPhil Dennis-Jordan
282352159cSPhil Dennis-Jordan#include <mach/mach.h>
292352159cSPhil Dennis-Jordan#include <mach/mach_vm.h>
302352159cSPhil Dennis-Jordan#include <dispatch/dispatch.h>
312352159cSPhil Dennis-Jordan
322352159cSPhil Dennis-Jordan#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h>
332352159cSPhil Dennis-Jordan
34*bb43a234SPhil Dennis-Jordanstatic const AppleGFXDisplayMode apple_gfx_default_modes[] = {
35*bb43a234SPhil Dennis-Jordan    { 1920, 1080, 60 },
36*bb43a234SPhil Dennis-Jordan    { 1440, 1080, 60 },
37*bb43a234SPhil Dennis-Jordan    { 1280, 1024, 60 },
382352159cSPhil Dennis-Jordan};
392352159cSPhil Dennis-Jordan
402352159cSPhil Dennis-Jordanstatic Error *apple_gfx_mig_blocker;
412352159cSPhil Dennis-Jordanstatic uint32_t next_pgdisplay_serial_num = 1;
422352159cSPhil Dennis-Jordan
432352159cSPhil Dennis-Jordanstatic dispatch_queue_t get_background_queue(void)
442352159cSPhil Dennis-Jordan{
452352159cSPhil Dennis-Jordan    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
462352159cSPhil Dennis-Jordan}
472352159cSPhil Dennis-Jordan
482352159cSPhil Dennis-Jordan/* ------ PGTask and task operations: new/destroy/map/unmap ------ */
492352159cSPhil Dennis-Jordan
502352159cSPhil Dennis-Jordan/*
512352159cSPhil Dennis-Jordan * This implements the type declared in <ParavirtualizedGraphics/PGDevice.h>
522352159cSPhil Dennis-Jordan * which is opaque from the framework's point of view. It is used in callbacks
532352159cSPhil Dennis-Jordan * in the form of its typedef PGTask_t, which also already exists in the
542352159cSPhil Dennis-Jordan * framework headers.
552352159cSPhil Dennis-Jordan *
562352159cSPhil Dennis-Jordan * A "task" in PVG terminology represents a host-virtual contiguous address
572352159cSPhil Dennis-Jordan * range which is reserved in a large chunk on task creation. The mapMemory
582352159cSPhil Dennis-Jordan * callback then requests ranges of guest system memory (identified by their
592352159cSPhil Dennis-Jordan * GPA) to be mapped into subranges of this reserved address space.
602352159cSPhil Dennis-Jordan * This type of operation isn't well-supported by QEMU's memory subsystem,
612352159cSPhil Dennis-Jordan * but it is fortunately trivial to achieve with Darwin's mach_vm_remap() call,
622352159cSPhil Dennis-Jordan * which allows us to refer to the same backing memory via multiple virtual
632352159cSPhil Dennis-Jordan * address ranges. The Mach VM APIs are therefore used throughout for managing
642352159cSPhil Dennis-Jordan * task memory.
652352159cSPhil Dennis-Jordan */
662352159cSPhil Dennis-Jordanstruct PGTask_s {
672352159cSPhil Dennis-Jordan    QTAILQ_ENTRY(PGTask_s) node;
682352159cSPhil Dennis-Jordan    AppleGFXState *s;
692352159cSPhil Dennis-Jordan    mach_vm_address_t address;
702352159cSPhil Dennis-Jordan    uint64_t len;
712352159cSPhil Dennis-Jordan    /*
722352159cSPhil Dennis-Jordan     * All unique MemoryRegions for which a mapping has been created in in this
732352159cSPhil Dennis-Jordan     * task, and on which we have thus called memory_region_ref(). There are
742352159cSPhil Dennis-Jordan     * usually very few regions of system RAM in total, so we expect this array
752352159cSPhil Dennis-Jordan     * to be very short. Therefore, no need for sorting or fancy search
762352159cSPhil Dennis-Jordan     * algorithms, linear search will do.
772352159cSPhil Dennis-Jordan     * Protected by AppleGFXState's task_mutex.
782352159cSPhil Dennis-Jordan     */
792352159cSPhil Dennis-Jordan    GPtrArray *mapped_regions;
802352159cSPhil Dennis-Jordan};
812352159cSPhil Dennis-Jordan
822352159cSPhil Dennis-Jordanstatic PGTask_t *apple_gfx_new_task(AppleGFXState *s, uint64_t len)
832352159cSPhil Dennis-Jordan{
842352159cSPhil Dennis-Jordan    mach_vm_address_t task_mem;
852352159cSPhil Dennis-Jordan    PGTask_t *task;
862352159cSPhil Dennis-Jordan    kern_return_t r;
872352159cSPhil Dennis-Jordan
882352159cSPhil Dennis-Jordan    r = mach_vm_allocate(mach_task_self(), &task_mem, len, VM_FLAGS_ANYWHERE);
892352159cSPhil Dennis-Jordan    if (r != KERN_SUCCESS) {
902352159cSPhil Dennis-Jordan        return NULL;
912352159cSPhil Dennis-Jordan    }
922352159cSPhil Dennis-Jordan
932352159cSPhil Dennis-Jordan    task = g_new0(PGTask_t, 1);
942352159cSPhil Dennis-Jordan    task->s = s;
952352159cSPhil Dennis-Jordan    task->address = task_mem;
962352159cSPhil Dennis-Jordan    task->len = len;
972352159cSPhil Dennis-Jordan    task->mapped_regions = g_ptr_array_sized_new(2 /* Usually enough */);
982352159cSPhil Dennis-Jordan
992352159cSPhil Dennis-Jordan    QEMU_LOCK_GUARD(&s->task_mutex);
1002352159cSPhil Dennis-Jordan    QTAILQ_INSERT_TAIL(&s->tasks, task, node);
1012352159cSPhil Dennis-Jordan
1022352159cSPhil Dennis-Jordan    return task;
1032352159cSPhil Dennis-Jordan}
1042352159cSPhil Dennis-Jordan
1052352159cSPhil Dennis-Jordanstatic void apple_gfx_destroy_task(AppleGFXState *s, PGTask_t *task)
1062352159cSPhil Dennis-Jordan{
1072352159cSPhil Dennis-Jordan    GPtrArray *regions = task->mapped_regions;
1082352159cSPhil Dennis-Jordan    MemoryRegion *region;
1092352159cSPhil Dennis-Jordan    size_t i;
1102352159cSPhil Dennis-Jordan
1112352159cSPhil Dennis-Jordan    for (i = 0; i < regions->len; ++i) {
1122352159cSPhil Dennis-Jordan        region = g_ptr_array_index(regions, i);
1132352159cSPhil Dennis-Jordan        memory_region_unref(region);
1142352159cSPhil Dennis-Jordan    }
1152352159cSPhil Dennis-Jordan    g_ptr_array_unref(regions);
1162352159cSPhil Dennis-Jordan
1172352159cSPhil Dennis-Jordan    mach_vm_deallocate(mach_task_self(), task->address, task->len);
1182352159cSPhil Dennis-Jordan
1192352159cSPhil Dennis-Jordan    QEMU_LOCK_GUARD(&s->task_mutex);
1202352159cSPhil Dennis-Jordan    QTAILQ_REMOVE(&s->tasks, task, node);
1212352159cSPhil Dennis-Jordan    g_free(task);
1222352159cSPhil Dennis-Jordan}
1232352159cSPhil Dennis-Jordan
1242352159cSPhil Dennis-Jordanvoid *apple_gfx_host_ptr_for_gpa_range(uint64_t guest_physical,
1252352159cSPhil Dennis-Jordan                                       uint64_t length, bool read_only,
1262352159cSPhil Dennis-Jordan                                       MemoryRegion **mapping_in_region)
1272352159cSPhil Dennis-Jordan{
1282352159cSPhil Dennis-Jordan    MemoryRegion *ram_region;
1292352159cSPhil Dennis-Jordan    char *host_ptr;
1302352159cSPhil Dennis-Jordan    hwaddr ram_region_offset = 0;
1312352159cSPhil Dennis-Jordan    hwaddr ram_region_length = length;
1322352159cSPhil Dennis-Jordan
1332352159cSPhil Dennis-Jordan    ram_region = address_space_translate(&address_space_memory,
1342352159cSPhil Dennis-Jordan                                         guest_physical,
1352352159cSPhil Dennis-Jordan                                         &ram_region_offset,
1362352159cSPhil Dennis-Jordan                                         &ram_region_length, !read_only,
1372352159cSPhil Dennis-Jordan                                         MEMTXATTRS_UNSPECIFIED);
1382352159cSPhil Dennis-Jordan
1392352159cSPhil Dennis-Jordan    if (!ram_region || ram_region_length < length ||
1402352159cSPhil Dennis-Jordan        !memory_access_is_direct(ram_region, !read_only)) {
1412352159cSPhil Dennis-Jordan        return NULL;
1422352159cSPhil Dennis-Jordan    }
1432352159cSPhil Dennis-Jordan
1442352159cSPhil Dennis-Jordan    host_ptr = memory_region_get_ram_ptr(ram_region);
1452352159cSPhil Dennis-Jordan    if (!host_ptr) {
1462352159cSPhil Dennis-Jordan        return NULL;
1472352159cSPhil Dennis-Jordan    }
1482352159cSPhil Dennis-Jordan    host_ptr += ram_region_offset;
1492352159cSPhil Dennis-Jordan    *mapping_in_region = ram_region;
1502352159cSPhil Dennis-Jordan    return host_ptr;
1512352159cSPhil Dennis-Jordan}
1522352159cSPhil Dennis-Jordan
1532352159cSPhil Dennis-Jordanstatic bool apple_gfx_task_map_memory(AppleGFXState *s, PGTask_t *task,
1542352159cSPhil Dennis-Jordan                                      uint64_t virtual_offset,
1552352159cSPhil Dennis-Jordan                                      PGPhysicalMemoryRange_t *ranges,
1562352159cSPhil Dennis-Jordan                                      uint32_t range_count, bool read_only)
1572352159cSPhil Dennis-Jordan{
1582352159cSPhil Dennis-Jordan    kern_return_t r;
1592352159cSPhil Dennis-Jordan    void *source_ptr;
1602352159cSPhil Dennis-Jordan    mach_vm_address_t target;
1612352159cSPhil Dennis-Jordan    vm_prot_t cur_protection, max_protection;
1622352159cSPhil Dennis-Jordan    bool success = true;
1632352159cSPhil Dennis-Jordan    MemoryRegion *region;
1642352159cSPhil Dennis-Jordan
1652352159cSPhil Dennis-Jordan    RCU_READ_LOCK_GUARD();
1662352159cSPhil Dennis-Jordan    QEMU_LOCK_GUARD(&s->task_mutex);
1672352159cSPhil Dennis-Jordan
1682352159cSPhil Dennis-Jordan    trace_apple_gfx_map_memory(task, range_count, virtual_offset, read_only);
1692352159cSPhil Dennis-Jordan    for (int i = 0; i < range_count; i++) {
1702352159cSPhil Dennis-Jordan        PGPhysicalMemoryRange_t *range = &ranges[i];
1712352159cSPhil Dennis-Jordan
1722352159cSPhil Dennis-Jordan        target = task->address + virtual_offset;
1732352159cSPhil Dennis-Jordan        virtual_offset += range->physicalLength;
1742352159cSPhil Dennis-Jordan
1752352159cSPhil Dennis-Jordan        trace_apple_gfx_map_memory_range(i, range->physicalAddress,
1762352159cSPhil Dennis-Jordan                                         range->physicalLength);
1772352159cSPhil Dennis-Jordan
1782352159cSPhil Dennis-Jordan        region = NULL;
1792352159cSPhil Dennis-Jordan        source_ptr = apple_gfx_host_ptr_for_gpa_range(range->physicalAddress,
1802352159cSPhil Dennis-Jordan                                                      range->physicalLength,
1812352159cSPhil Dennis-Jordan                                                      read_only, &region);
1822352159cSPhil Dennis-Jordan        if (!source_ptr) {
1832352159cSPhil Dennis-Jordan            success = false;
1842352159cSPhil Dennis-Jordan            continue;
1852352159cSPhil Dennis-Jordan        }
1862352159cSPhil Dennis-Jordan
1872352159cSPhil Dennis-Jordan        if (!g_ptr_array_find(task->mapped_regions, region, NULL)) {
1882352159cSPhil Dennis-Jordan            g_ptr_array_add(task->mapped_regions, region);
1892352159cSPhil Dennis-Jordan            memory_region_ref(region);
1902352159cSPhil Dennis-Jordan        }
1912352159cSPhil Dennis-Jordan
1922352159cSPhil Dennis-Jordan        cur_protection = 0;
1932352159cSPhil Dennis-Jordan        max_protection = 0;
1942352159cSPhil Dennis-Jordan        /* Map guest RAM at range->physicalAddress into PG task memory range */
1952352159cSPhil Dennis-Jordan        r = mach_vm_remap(mach_task_self(),
1962352159cSPhil Dennis-Jordan                          &target, range->physicalLength, vm_page_size - 1,
1972352159cSPhil Dennis-Jordan                          VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
1982352159cSPhil Dennis-Jordan                          mach_task_self(), (mach_vm_address_t)source_ptr,
1992352159cSPhil Dennis-Jordan                          false /* shared mapping, no copy */,
2002352159cSPhil Dennis-Jordan                          &cur_protection, &max_protection,
2012352159cSPhil Dennis-Jordan                          VM_INHERIT_COPY);
2022352159cSPhil Dennis-Jordan        trace_apple_gfx_remap(r, source_ptr, target);
2032352159cSPhil Dennis-Jordan        g_assert(r == KERN_SUCCESS);
2042352159cSPhil Dennis-Jordan    }
2052352159cSPhil Dennis-Jordan
2062352159cSPhil Dennis-Jordan    return success;
2072352159cSPhil Dennis-Jordan}
2082352159cSPhil Dennis-Jordan
2092352159cSPhil Dennis-Jordanstatic void apple_gfx_task_unmap_memory(AppleGFXState *s, PGTask_t *task,
2102352159cSPhil Dennis-Jordan                                        uint64_t virtual_offset, uint64_t length)
2112352159cSPhil Dennis-Jordan{
2122352159cSPhil Dennis-Jordan    kern_return_t r;
2132352159cSPhil Dennis-Jordan    mach_vm_address_t range_address;
2142352159cSPhil Dennis-Jordan
2152352159cSPhil Dennis-Jordan    trace_apple_gfx_unmap_memory(task, virtual_offset, length);
2162352159cSPhil Dennis-Jordan
2172352159cSPhil Dennis-Jordan    /*
2182352159cSPhil Dennis-Jordan     * Replace task memory range with fresh 0 pages, undoing the mapping
2192352159cSPhil Dennis-Jordan     * from guest RAM.
2202352159cSPhil Dennis-Jordan     */
2212352159cSPhil Dennis-Jordan    range_address = task->address + virtual_offset;
2222352159cSPhil Dennis-Jordan    r = mach_vm_allocate(mach_task_self(), &range_address, length,
2232352159cSPhil Dennis-Jordan                         VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE);
2242352159cSPhil Dennis-Jordan    g_assert(r == KERN_SUCCESS);
2252352159cSPhil Dennis-Jordan}
2262352159cSPhil Dennis-Jordan
2272352159cSPhil Dennis-Jordan/* ------ Rendering and frame management ------ */
2282352159cSPhil Dennis-Jordan
2292352159cSPhil Dennis-Jordanstatic void apple_gfx_render_frame_completed_bh(void *opaque);
2302352159cSPhil Dennis-Jordan
2312352159cSPhil Dennis-Jordanstatic void apple_gfx_render_new_frame(AppleGFXState *s)
2322352159cSPhil Dennis-Jordan{
2332352159cSPhil Dennis-Jordan    bool managed_texture = s->using_managed_texture_storage;
2342352159cSPhil Dennis-Jordan    uint32_t width = surface_width(s->surface);
2352352159cSPhil Dennis-Jordan    uint32_t height = surface_height(s->surface);
2362352159cSPhil Dennis-Jordan    MTLRegion region = MTLRegionMake2D(0, 0, width, height);
2372352159cSPhil Dennis-Jordan    id<MTLCommandBuffer> command_buffer = [s->mtl_queue commandBuffer];
2382352159cSPhil Dennis-Jordan    id<MTLTexture> texture = s->texture;
2392352159cSPhil Dennis-Jordan
2402352159cSPhil Dennis-Jordan    assert(bql_locked());
2412352159cSPhil Dennis-Jordan    [texture retain];
2422352159cSPhil Dennis-Jordan    [command_buffer retain];
2432352159cSPhil Dennis-Jordan
2442352159cSPhil Dennis-Jordan    s->rendering_frame_width = width;
2452352159cSPhil Dennis-Jordan    s->rendering_frame_height = height;
2462352159cSPhil Dennis-Jordan
2472352159cSPhil Dennis-Jordan    dispatch_async(get_background_queue(), ^{
2482352159cSPhil Dennis-Jordan        /*
2492352159cSPhil Dennis-Jordan         * This is not safe to call from the BQL/BH due to PVG-internal locks
2502352159cSPhil Dennis-Jordan         * causing deadlocks.
2512352159cSPhil Dennis-Jordan         */
2522352159cSPhil Dennis-Jordan        bool r = [s->pgdisp encodeCurrentFrameToCommandBuffer:command_buffer
2532352159cSPhil Dennis-Jordan                                                 texture:texture
2542352159cSPhil Dennis-Jordan                                                  region:region];
2552352159cSPhil Dennis-Jordan        if (!r) {
2562352159cSPhil Dennis-Jordan            [texture release];
2572352159cSPhil Dennis-Jordan            [command_buffer release];
2582352159cSPhil Dennis-Jordan            qemu_log_mask(LOG_GUEST_ERROR,
2592352159cSPhil Dennis-Jordan                          "%s: encodeCurrentFrameToCommandBuffer:texture:region: "
2602352159cSPhil Dennis-Jordan                          "failed\n", __func__);
2612352159cSPhil Dennis-Jordan            bql_lock();
2622352159cSPhil Dennis-Jordan            --s->pending_frames;
2632352159cSPhil Dennis-Jordan            if (s->pending_frames > 0) {
2642352159cSPhil Dennis-Jordan                apple_gfx_render_new_frame(s);
2652352159cSPhil Dennis-Jordan            }
2662352159cSPhil Dennis-Jordan            bql_unlock();
2672352159cSPhil Dennis-Jordan            return;
2682352159cSPhil Dennis-Jordan        }
2692352159cSPhil Dennis-Jordan
2702352159cSPhil Dennis-Jordan        if (managed_texture) {
2712352159cSPhil Dennis-Jordan            /* "Managed" textures exist in both VRAM and RAM and must be synced. */
2722352159cSPhil Dennis-Jordan            id<MTLBlitCommandEncoder> blit = [command_buffer blitCommandEncoder];
2732352159cSPhil Dennis-Jordan            [blit synchronizeResource:texture];
2742352159cSPhil Dennis-Jordan            [blit endEncoding];
2752352159cSPhil Dennis-Jordan        }
2762352159cSPhil Dennis-Jordan        [texture release];
2772352159cSPhil Dennis-Jordan        [command_buffer addCompletedHandler:
2782352159cSPhil Dennis-Jordan            ^(id<MTLCommandBuffer> cb)
2792352159cSPhil Dennis-Jordan            {
2802352159cSPhil Dennis-Jordan                aio_bh_schedule_oneshot(qemu_get_aio_context(),
2812352159cSPhil Dennis-Jordan                                        apple_gfx_render_frame_completed_bh, s);
2822352159cSPhil Dennis-Jordan            }];
2832352159cSPhil Dennis-Jordan        [command_buffer commit];
2842352159cSPhil Dennis-Jordan        [command_buffer release];
2852352159cSPhil Dennis-Jordan    });
2862352159cSPhil Dennis-Jordan}
2872352159cSPhil Dennis-Jordan
2882352159cSPhil Dennis-Jordanstatic void copy_mtl_texture_to_surface_mem(id<MTLTexture> texture, void *vram)
2892352159cSPhil Dennis-Jordan{
2902352159cSPhil Dennis-Jordan    /*
2912352159cSPhil Dennis-Jordan     * TODO: Skip this entirely on a pure Metal or headless/guest-only
2922352159cSPhil Dennis-Jordan     * rendering path, else use a blit command encoder? Needs careful
2932352159cSPhil Dennis-Jordan     * (double?) buffering design.
2942352159cSPhil Dennis-Jordan     */
2952352159cSPhil Dennis-Jordan    size_t width = texture.width, height = texture.height;
2962352159cSPhil Dennis-Jordan    MTLRegion region = MTLRegionMake2D(0, 0, width, height);
2972352159cSPhil Dennis-Jordan    [texture getBytes:vram
2982352159cSPhil Dennis-Jordan          bytesPerRow:(width * 4)
2992352159cSPhil Dennis-Jordan        bytesPerImage:(width * height * 4)
3002352159cSPhil Dennis-Jordan           fromRegion:region
3012352159cSPhil Dennis-Jordan          mipmapLevel:0
3022352159cSPhil Dennis-Jordan                slice:0];
3032352159cSPhil Dennis-Jordan}
3042352159cSPhil Dennis-Jordan
3052352159cSPhil Dennis-Jordanstatic void apple_gfx_render_frame_completed_bh(void *opaque)
3062352159cSPhil Dennis-Jordan{
3072352159cSPhil Dennis-Jordan    AppleGFXState *s = opaque;
3082352159cSPhil Dennis-Jordan
3092352159cSPhil Dennis-Jordan    @autoreleasepool {
3102352159cSPhil Dennis-Jordan        --s->pending_frames;
3112352159cSPhil Dennis-Jordan        assert(s->pending_frames >= 0);
3122352159cSPhil Dennis-Jordan
3132352159cSPhil Dennis-Jordan        /* Only update display if mode hasn't changed since we started rendering. */
3142352159cSPhil Dennis-Jordan        if (s->rendering_frame_width == surface_width(s->surface) &&
3152352159cSPhil Dennis-Jordan            s->rendering_frame_height == surface_height(s->surface)) {
3162352159cSPhil Dennis-Jordan            copy_mtl_texture_to_surface_mem(s->texture, surface_data(s->surface));
3172352159cSPhil Dennis-Jordan            if (s->gfx_update_requested) {
3182352159cSPhil Dennis-Jordan                s->gfx_update_requested = false;
3192352159cSPhil Dennis-Jordan                dpy_gfx_update_full(s->con);
3202352159cSPhil Dennis-Jordan                graphic_hw_update_done(s->con);
3212352159cSPhil Dennis-Jordan                s->new_frame_ready = false;
3222352159cSPhil Dennis-Jordan            } else {
3232352159cSPhil Dennis-Jordan                s->new_frame_ready = true;
3242352159cSPhil Dennis-Jordan            }
3252352159cSPhil Dennis-Jordan        }
3262352159cSPhil Dennis-Jordan        if (s->pending_frames > 0) {
3272352159cSPhil Dennis-Jordan            apple_gfx_render_new_frame(s);
3282352159cSPhil Dennis-Jordan        }
3292352159cSPhil Dennis-Jordan    }
3302352159cSPhil Dennis-Jordan}
3312352159cSPhil Dennis-Jordan
3322352159cSPhil Dennis-Jordanstatic void apple_gfx_fb_update_display(void *opaque)
3332352159cSPhil Dennis-Jordan{
3342352159cSPhil Dennis-Jordan    AppleGFXState *s = opaque;
3352352159cSPhil Dennis-Jordan
3362352159cSPhil Dennis-Jordan    assert(bql_locked());
3372352159cSPhil Dennis-Jordan    if (s->new_frame_ready) {
3382352159cSPhil Dennis-Jordan        dpy_gfx_update_full(s->con);
3392352159cSPhil Dennis-Jordan        s->new_frame_ready = false;
3402352159cSPhil Dennis-Jordan        graphic_hw_update_done(s->con);
3412352159cSPhil Dennis-Jordan    } else if (s->pending_frames > 0) {
3422352159cSPhil Dennis-Jordan        s->gfx_update_requested = true;
3432352159cSPhil Dennis-Jordan    } else {
3442352159cSPhil Dennis-Jordan        graphic_hw_update_done(s->con);
3452352159cSPhil Dennis-Jordan    }
3462352159cSPhil Dennis-Jordan}
3472352159cSPhil Dennis-Jordan
3482352159cSPhil Dennis-Jordanstatic const GraphicHwOps apple_gfx_fb_ops = {
3492352159cSPhil Dennis-Jordan    .gfx_update = apple_gfx_fb_update_display,
3502352159cSPhil Dennis-Jordan    .gfx_update_async = true,
3512352159cSPhil Dennis-Jordan};
3522352159cSPhil Dennis-Jordan
3532352159cSPhil Dennis-Jordan/* ------ Mouse cursor and display mode setting ------ */
3542352159cSPhil Dennis-Jordan
3552352159cSPhil Dennis-Jordanstatic void set_mode(AppleGFXState *s, uint32_t width, uint32_t height)
3562352159cSPhil Dennis-Jordan{
3572352159cSPhil Dennis-Jordan    MTLTextureDescriptor *textureDescriptor;
3582352159cSPhil Dennis-Jordan
3592352159cSPhil Dennis-Jordan    if (s->surface &&
3602352159cSPhil Dennis-Jordan        width == surface_width(s->surface) &&
3612352159cSPhil Dennis-Jordan        height == surface_height(s->surface)) {
3622352159cSPhil Dennis-Jordan        return;
3632352159cSPhil Dennis-Jordan    }
3642352159cSPhil Dennis-Jordan
3652352159cSPhil Dennis-Jordan    [s->texture release];
3662352159cSPhil Dennis-Jordan
3672352159cSPhil Dennis-Jordan    s->surface = qemu_create_displaysurface(width, height);
3682352159cSPhil Dennis-Jordan
3692352159cSPhil Dennis-Jordan    @autoreleasepool {
3702352159cSPhil Dennis-Jordan        textureDescriptor =
3712352159cSPhil Dennis-Jordan            [MTLTextureDescriptor
3722352159cSPhil Dennis-Jordan                texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
3732352159cSPhil Dennis-Jordan                                             width:width
3742352159cSPhil Dennis-Jordan                                            height:height
3752352159cSPhil Dennis-Jordan                                         mipmapped:NO];
3762352159cSPhil Dennis-Jordan        textureDescriptor.usage = s->pgdisp.minimumTextureUsage;
3772352159cSPhil Dennis-Jordan        s->texture = [s->mtl newTextureWithDescriptor:textureDescriptor];
3782352159cSPhil Dennis-Jordan        s->using_managed_texture_storage =
3792352159cSPhil Dennis-Jordan            (s->texture.storageMode == MTLStorageModeManaged);
3802352159cSPhil Dennis-Jordan    }
3812352159cSPhil Dennis-Jordan
3822352159cSPhil Dennis-Jordan    dpy_gfx_replace_surface(s->con, s->surface);
3832352159cSPhil Dennis-Jordan}
3842352159cSPhil Dennis-Jordan
3852352159cSPhil Dennis-Jordanstatic void update_cursor(AppleGFXState *s)
3862352159cSPhil Dennis-Jordan{
3872352159cSPhil Dennis-Jordan    assert(bql_locked());
3882352159cSPhil Dennis-Jordan    dpy_mouse_set(s->con, s->pgdisp.cursorPosition.x,
3892352159cSPhil Dennis-Jordan                  s->pgdisp.cursorPosition.y, qatomic_read(&s->cursor_show));
3902352159cSPhil Dennis-Jordan}
3912352159cSPhil Dennis-Jordan
3922352159cSPhil Dennis-Jordanstatic void update_cursor_bh(void *opaque)
3932352159cSPhil Dennis-Jordan{
3942352159cSPhil Dennis-Jordan    AppleGFXState *s = opaque;
3952352159cSPhil Dennis-Jordan    update_cursor(s);
3962352159cSPhil Dennis-Jordan}
3972352159cSPhil Dennis-Jordan
3982352159cSPhil Dennis-Jordantypedef struct AppleGFXSetCursorGlyphJob {
3992352159cSPhil Dennis-Jordan    AppleGFXState *s;
4002352159cSPhil Dennis-Jordan    NSBitmapImageRep *glyph;
4012352159cSPhil Dennis-Jordan    PGDisplayCoord_t hotspot;
4022352159cSPhil Dennis-Jordan} AppleGFXSetCursorGlyphJob;
4032352159cSPhil Dennis-Jordan
4042352159cSPhil Dennis-Jordanstatic void set_cursor_glyph(void *opaque)
4052352159cSPhil Dennis-Jordan{
4062352159cSPhil Dennis-Jordan    AppleGFXSetCursorGlyphJob *job = opaque;
4072352159cSPhil Dennis-Jordan    AppleGFXState *s = job->s;
4082352159cSPhil Dennis-Jordan    NSBitmapImageRep *glyph = job->glyph;
4092352159cSPhil Dennis-Jordan    uint32_t bpp = glyph.bitsPerPixel;
4102352159cSPhil Dennis-Jordan    size_t width = glyph.pixelsWide;
4112352159cSPhil Dennis-Jordan    size_t height = glyph.pixelsHigh;
4122352159cSPhil Dennis-Jordan    size_t padding_bytes_per_row = glyph.bytesPerRow - width * 4;
4132352159cSPhil Dennis-Jordan    const uint8_t* px_data = glyph.bitmapData;
4142352159cSPhil Dennis-Jordan
4152352159cSPhil Dennis-Jordan    trace_apple_gfx_cursor_set(bpp, width, height);
4162352159cSPhil Dennis-Jordan
4172352159cSPhil Dennis-Jordan    if (s->cursor) {
4182352159cSPhil Dennis-Jordan        cursor_unref(s->cursor);
4192352159cSPhil Dennis-Jordan        s->cursor = NULL;
4202352159cSPhil Dennis-Jordan    }
4212352159cSPhil Dennis-Jordan
4222352159cSPhil Dennis-Jordan    if (bpp == 32) { /* Shouldn't be anything else, but just to be safe... */
4232352159cSPhil Dennis-Jordan        s->cursor = cursor_alloc(width, height);
4242352159cSPhil Dennis-Jordan        s->cursor->hot_x = job->hotspot.x;
4252352159cSPhil Dennis-Jordan        s->cursor->hot_y = job->hotspot.y;
4262352159cSPhil Dennis-Jordan
4272352159cSPhil Dennis-Jordan        uint32_t *dest_px = s->cursor->data;
4282352159cSPhil Dennis-Jordan
4292352159cSPhil Dennis-Jordan        for (size_t y = 0; y < height; ++y) {
4302352159cSPhil Dennis-Jordan            for (size_t x = 0; x < width; ++x) {
4312352159cSPhil Dennis-Jordan                /*
4322352159cSPhil Dennis-Jordan                 * NSBitmapImageRep's red & blue channels are swapped
4332352159cSPhil Dennis-Jordan                 * compared to QEMUCursor's.
4342352159cSPhil Dennis-Jordan                 */
4352352159cSPhil Dennis-Jordan                *dest_px =
4362352159cSPhil Dennis-Jordan                    (px_data[0] << 16u) |
4372352159cSPhil Dennis-Jordan                    (px_data[1] <<  8u) |
4382352159cSPhil Dennis-Jordan                    (px_data[2] <<  0u) |
4392352159cSPhil Dennis-Jordan                    (px_data[3] << 24u);
4402352159cSPhil Dennis-Jordan                ++dest_px;
4412352159cSPhil Dennis-Jordan                px_data += 4;
4422352159cSPhil Dennis-Jordan            }
4432352159cSPhil Dennis-Jordan            px_data += padding_bytes_per_row;
4442352159cSPhil Dennis-Jordan        }
4452352159cSPhil Dennis-Jordan        dpy_cursor_define(s->con, s->cursor);
4462352159cSPhil Dennis-Jordan        update_cursor(s);
4472352159cSPhil Dennis-Jordan    }
4482352159cSPhil Dennis-Jordan    [glyph release];
4492352159cSPhil Dennis-Jordan
4502352159cSPhil Dennis-Jordan    g_free(job);
4512352159cSPhil Dennis-Jordan}
4522352159cSPhil Dennis-Jordan
4532352159cSPhil Dennis-Jordan/* ------ DMA (device reading system memory) ------ */
4542352159cSPhil Dennis-Jordan
4552352159cSPhil Dennis-Jordantypedef struct AppleGFXReadMemoryJob {
4562352159cSPhil Dennis-Jordan    QemuSemaphore sem;
4572352159cSPhil Dennis-Jordan    hwaddr physical_address;
4582352159cSPhil Dennis-Jordan    uint64_t length;
4592352159cSPhil Dennis-Jordan    void *dst;
4602352159cSPhil Dennis-Jordan    bool success;
4612352159cSPhil Dennis-Jordan} AppleGFXReadMemoryJob;
4622352159cSPhil Dennis-Jordan
4632352159cSPhil Dennis-Jordanstatic void apple_gfx_do_read_memory(void *opaque)
4642352159cSPhil Dennis-Jordan{
4652352159cSPhil Dennis-Jordan    AppleGFXReadMemoryJob *job = opaque;
4662352159cSPhil Dennis-Jordan    MemTxResult r;
4672352159cSPhil Dennis-Jordan
4682352159cSPhil Dennis-Jordan    r = dma_memory_read(&address_space_memory, job->physical_address,
4692352159cSPhil Dennis-Jordan                        job->dst, job->length, MEMTXATTRS_UNSPECIFIED);
4702352159cSPhil Dennis-Jordan    job->success = (r == MEMTX_OK);
4712352159cSPhil Dennis-Jordan
4722352159cSPhil Dennis-Jordan    qemu_sem_post(&job->sem);
4732352159cSPhil Dennis-Jordan}
4742352159cSPhil Dennis-Jordan
4752352159cSPhil Dennis-Jordanstatic bool apple_gfx_read_memory(AppleGFXState *s, hwaddr physical_address,
4762352159cSPhil Dennis-Jordan                                  uint64_t length, void *dst)
4772352159cSPhil Dennis-Jordan{
4782352159cSPhil Dennis-Jordan    AppleGFXReadMemoryJob job = {
4792352159cSPhil Dennis-Jordan        .physical_address = physical_address, .length = length, .dst = dst
4802352159cSPhil Dennis-Jordan    };
4812352159cSPhil Dennis-Jordan
4822352159cSPhil Dennis-Jordan    trace_apple_gfx_read_memory(physical_address, length, dst);
4832352159cSPhil Dennis-Jordan
4842352159cSPhil Dennis-Jordan    /* Performing DMA requires BQL, so do it in a BH. */
4852352159cSPhil Dennis-Jordan    qemu_sem_init(&job.sem, 0);
4862352159cSPhil Dennis-Jordan    aio_bh_schedule_oneshot(qemu_get_aio_context(),
4872352159cSPhil Dennis-Jordan                            apple_gfx_do_read_memory, &job);
4882352159cSPhil Dennis-Jordan    qemu_sem_wait(&job.sem);
4892352159cSPhil Dennis-Jordan    qemu_sem_destroy(&job.sem);
4902352159cSPhil Dennis-Jordan    return job.success;
4912352159cSPhil Dennis-Jordan}
4922352159cSPhil Dennis-Jordan
4932352159cSPhil Dennis-Jordan/* ------ Memory-mapped device I/O operations ------ */
4942352159cSPhil Dennis-Jordan
4952352159cSPhil Dennis-Jordantypedef struct AppleGFXIOJob {
4962352159cSPhil Dennis-Jordan    AppleGFXState *state;
4972352159cSPhil Dennis-Jordan    uint64_t offset;
4982352159cSPhil Dennis-Jordan    uint64_t value;
4992352159cSPhil Dennis-Jordan    bool completed;
5002352159cSPhil Dennis-Jordan} AppleGFXIOJob;
5012352159cSPhil Dennis-Jordan
5022352159cSPhil Dennis-Jordanstatic void apple_gfx_do_read(void *opaque)
5032352159cSPhil Dennis-Jordan{
5042352159cSPhil Dennis-Jordan    AppleGFXIOJob *job = opaque;
5052352159cSPhil Dennis-Jordan    job->value = [job->state->pgdev mmioReadAtOffset:job->offset];
5062352159cSPhil Dennis-Jordan    qatomic_set(&job->completed, true);
5072352159cSPhil Dennis-Jordan    aio_wait_kick();
5082352159cSPhil Dennis-Jordan}
5092352159cSPhil Dennis-Jordan
5102352159cSPhil Dennis-Jordanstatic uint64_t apple_gfx_read(void *opaque, hwaddr offset, unsigned size)
5112352159cSPhil Dennis-Jordan{
5122352159cSPhil Dennis-Jordan    AppleGFXIOJob job = {
5132352159cSPhil Dennis-Jordan        .state = opaque,
5142352159cSPhil Dennis-Jordan        .offset = offset,
5152352159cSPhil Dennis-Jordan        .completed = false,
5162352159cSPhil Dennis-Jordan    };
5172352159cSPhil Dennis-Jordan    dispatch_queue_t queue = get_background_queue();
5182352159cSPhil Dennis-Jordan
5192352159cSPhil Dennis-Jordan    dispatch_async_f(queue, &job, apple_gfx_do_read);
5202352159cSPhil Dennis-Jordan    AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
5212352159cSPhil Dennis-Jordan
5222352159cSPhil Dennis-Jordan    trace_apple_gfx_read(offset, job.value);
5232352159cSPhil Dennis-Jordan    return job.value;
5242352159cSPhil Dennis-Jordan}
5252352159cSPhil Dennis-Jordan
5262352159cSPhil Dennis-Jordanstatic void apple_gfx_do_write(void *opaque)
5272352159cSPhil Dennis-Jordan{
5282352159cSPhil Dennis-Jordan    AppleGFXIOJob *job = opaque;
5292352159cSPhil Dennis-Jordan    [job->state->pgdev mmioWriteAtOffset:job->offset value:job->value];
5302352159cSPhil Dennis-Jordan    qatomic_set(&job->completed, true);
5312352159cSPhil Dennis-Jordan    aio_wait_kick();
5322352159cSPhil Dennis-Jordan}
5332352159cSPhil Dennis-Jordan
5342352159cSPhil Dennis-Jordanstatic void apple_gfx_write(void *opaque, hwaddr offset, uint64_t val,
5352352159cSPhil Dennis-Jordan                            unsigned size)
5362352159cSPhil Dennis-Jordan{
5372352159cSPhil Dennis-Jordan    /*
5382352159cSPhil Dennis-Jordan     * The methods mmioReadAtOffset: and especially mmioWriteAtOffset: can
5392352159cSPhil Dennis-Jordan     * trigger synchronous operations on other dispatch queues, which in turn
5402352159cSPhil Dennis-Jordan     * may call back out on one or more of the callback blocks. For this reason,
5412352159cSPhil Dennis-Jordan     * and as we are holding the BQL, we invoke the I/O methods on a pool
5422352159cSPhil Dennis-Jordan     * thread and handle AIO tasks while we wait. Any work in the callbacks
5432352159cSPhil Dennis-Jordan     * requiring the BQL will in turn schedule BHs which this thread will
5442352159cSPhil Dennis-Jordan     * process while waiting.
5452352159cSPhil Dennis-Jordan     */
5462352159cSPhil Dennis-Jordan    AppleGFXIOJob job = {
5472352159cSPhil Dennis-Jordan        .state = opaque,
5482352159cSPhil Dennis-Jordan        .offset = offset,
5492352159cSPhil Dennis-Jordan        .value = val,
5502352159cSPhil Dennis-Jordan        .completed = false,
5512352159cSPhil Dennis-Jordan    };
5522352159cSPhil Dennis-Jordan    dispatch_queue_t queue = get_background_queue();
5532352159cSPhil Dennis-Jordan
5542352159cSPhil Dennis-Jordan    dispatch_async_f(queue, &job, apple_gfx_do_write);
5552352159cSPhil Dennis-Jordan    AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
5562352159cSPhil Dennis-Jordan
5572352159cSPhil Dennis-Jordan    trace_apple_gfx_write(offset, val);
5582352159cSPhil Dennis-Jordan}
5592352159cSPhil Dennis-Jordan
5602352159cSPhil Dennis-Jordanstatic const MemoryRegionOps apple_gfx_ops = {
5612352159cSPhil Dennis-Jordan    .read = apple_gfx_read,
5622352159cSPhil Dennis-Jordan    .write = apple_gfx_write,
5632352159cSPhil Dennis-Jordan    .endianness = DEVICE_LITTLE_ENDIAN,
5642352159cSPhil Dennis-Jordan    .valid = {
5652352159cSPhil Dennis-Jordan        .min_access_size = 4,
5662352159cSPhil Dennis-Jordan        .max_access_size = 8,
5672352159cSPhil Dennis-Jordan    },
5682352159cSPhil Dennis-Jordan    .impl = {
5692352159cSPhil Dennis-Jordan        .min_access_size = 4,
5702352159cSPhil Dennis-Jordan        .max_access_size = 4,
5712352159cSPhil Dennis-Jordan    },
5722352159cSPhil Dennis-Jordan};
5732352159cSPhil Dennis-Jordan
5742352159cSPhil Dennis-Jordanstatic size_t apple_gfx_get_default_mmio_range_size(void)
5752352159cSPhil Dennis-Jordan{
5762352159cSPhil Dennis-Jordan    size_t mmio_range_size;
5772352159cSPhil Dennis-Jordan    @autoreleasepool {
5782352159cSPhil Dennis-Jordan        PGDeviceDescriptor *desc = [PGDeviceDescriptor new];
5792352159cSPhil Dennis-Jordan        mmio_range_size = desc.mmioLength;
5802352159cSPhil Dennis-Jordan        [desc release];
5812352159cSPhil Dennis-Jordan    }
5822352159cSPhil Dennis-Jordan    return mmio_range_size;
5832352159cSPhil Dennis-Jordan}
5842352159cSPhil Dennis-Jordan
5852352159cSPhil Dennis-Jordan/* ------ Initialisation and startup ------ */
5862352159cSPhil Dennis-Jordan
5872352159cSPhil Dennis-Jordanvoid apple_gfx_common_init(Object *obj, AppleGFXState *s, const char* obj_name)
5882352159cSPhil Dennis-Jordan{
5892352159cSPhil Dennis-Jordan    size_t mmio_range_size = apple_gfx_get_default_mmio_range_size();
5902352159cSPhil Dennis-Jordan
5912352159cSPhil Dennis-Jordan    trace_apple_gfx_common_init(obj_name, mmio_range_size);
5922352159cSPhil Dennis-Jordan    memory_region_init_io(&s->iomem_gfx, obj, &apple_gfx_ops, s, obj_name,
5932352159cSPhil Dennis-Jordan                          mmio_range_size);
5942352159cSPhil Dennis-Jordan
5952352159cSPhil Dennis-Jordan    /* TODO: PVG framework supports serialising device state: integrate it! */
5962352159cSPhil Dennis-Jordan}
5972352159cSPhil Dennis-Jordan
5982352159cSPhil Dennis-Jordanstatic void apple_gfx_register_task_mapping_handlers(AppleGFXState *s,
5992352159cSPhil Dennis-Jordan                                                     PGDeviceDescriptor *desc)
6002352159cSPhil Dennis-Jordan{
6012352159cSPhil Dennis-Jordan    desc.createTask = ^(uint64_t vmSize, void * _Nullable * _Nonnull baseAddress) {
6022352159cSPhil Dennis-Jordan        PGTask_t *task = apple_gfx_new_task(s, vmSize);
6032352159cSPhil Dennis-Jordan        *baseAddress = (void *)task->address;
6042352159cSPhil Dennis-Jordan        trace_apple_gfx_create_task(vmSize, *baseAddress);
6052352159cSPhil Dennis-Jordan        return task;
6062352159cSPhil Dennis-Jordan    };
6072352159cSPhil Dennis-Jordan
6082352159cSPhil Dennis-Jordan    desc.destroyTask = ^(PGTask_t * _Nonnull task) {
6092352159cSPhil Dennis-Jordan        trace_apple_gfx_destroy_task(task, task->mapped_regions->len);
6102352159cSPhil Dennis-Jordan
6112352159cSPhil Dennis-Jordan        apple_gfx_destroy_task(s, task);
6122352159cSPhil Dennis-Jordan    };
6132352159cSPhil Dennis-Jordan
6142352159cSPhil Dennis-Jordan    desc.mapMemory = ^bool(PGTask_t * _Nonnull task, uint32_t range_count,
6152352159cSPhil Dennis-Jordan                           uint64_t virtual_offset, bool read_only,
6162352159cSPhil Dennis-Jordan                           PGPhysicalMemoryRange_t * _Nonnull ranges) {
6172352159cSPhil Dennis-Jordan        return apple_gfx_task_map_memory(s, task, virtual_offset,
6182352159cSPhil Dennis-Jordan                                         ranges, range_count, read_only);
6192352159cSPhil Dennis-Jordan    };
6202352159cSPhil Dennis-Jordan
6212352159cSPhil Dennis-Jordan    desc.unmapMemory = ^bool(PGTask_t * _Nonnull task, uint64_t virtual_offset,
6222352159cSPhil Dennis-Jordan                             uint64_t length) {
6232352159cSPhil Dennis-Jordan        apple_gfx_task_unmap_memory(s, task, virtual_offset, length);
6242352159cSPhil Dennis-Jordan        return true;
6252352159cSPhil Dennis-Jordan    };
6262352159cSPhil Dennis-Jordan
6272352159cSPhil Dennis-Jordan    desc.readMemory = ^bool(uint64_t physical_address, uint64_t length,
6282352159cSPhil Dennis-Jordan                            void * _Nonnull dst) {
6292352159cSPhil Dennis-Jordan        return apple_gfx_read_memory(s, physical_address, length, dst);
6302352159cSPhil Dennis-Jordan    };
6312352159cSPhil Dennis-Jordan}
6322352159cSPhil Dennis-Jordan
6332352159cSPhil Dennis-Jordanstatic void new_frame_handler_bh(void *opaque)
6342352159cSPhil Dennis-Jordan{
6352352159cSPhil Dennis-Jordan    AppleGFXState *s = opaque;
6362352159cSPhil Dennis-Jordan
6372352159cSPhil Dennis-Jordan    /* Drop frames if guest gets too far ahead. */
6382352159cSPhil Dennis-Jordan    if (s->pending_frames >= 2) {
6392352159cSPhil Dennis-Jordan        return;
6402352159cSPhil Dennis-Jordan    }
6412352159cSPhil Dennis-Jordan    ++s->pending_frames;
6422352159cSPhil Dennis-Jordan    if (s->pending_frames > 1) {
6432352159cSPhil Dennis-Jordan        return;
6442352159cSPhil Dennis-Jordan    }
6452352159cSPhil Dennis-Jordan
6462352159cSPhil Dennis-Jordan    @autoreleasepool {
6472352159cSPhil Dennis-Jordan        apple_gfx_render_new_frame(s);
6482352159cSPhil Dennis-Jordan    }
6492352159cSPhil Dennis-Jordan}
6502352159cSPhil Dennis-Jordan
6512352159cSPhil Dennis-Jordanstatic PGDisplayDescriptor *apple_gfx_prepare_display_descriptor(AppleGFXState *s)
6522352159cSPhil Dennis-Jordan{
6532352159cSPhil Dennis-Jordan    PGDisplayDescriptor *disp_desc = [PGDisplayDescriptor new];
6542352159cSPhil Dennis-Jordan
6552352159cSPhil Dennis-Jordan    disp_desc.name = @"QEMU display";
6562352159cSPhil Dennis-Jordan    disp_desc.sizeInMillimeters = NSMakeSize(400., 300.); /* A 20" display */
6572352159cSPhil Dennis-Jordan    disp_desc.queue = dispatch_get_main_queue();
6582352159cSPhil Dennis-Jordan    disp_desc.newFrameEventHandler = ^(void) {
6592352159cSPhil Dennis-Jordan        trace_apple_gfx_new_frame();
6602352159cSPhil Dennis-Jordan        aio_bh_schedule_oneshot(qemu_get_aio_context(), new_frame_handler_bh, s);
6612352159cSPhil Dennis-Jordan    };
6622352159cSPhil Dennis-Jordan    disp_desc.modeChangeHandler = ^(PGDisplayCoord_t sizeInPixels,
6632352159cSPhil Dennis-Jordan                                    OSType pixelFormat) {
6642352159cSPhil Dennis-Jordan        trace_apple_gfx_mode_change(sizeInPixels.x, sizeInPixels.y);
6652352159cSPhil Dennis-Jordan
6662352159cSPhil Dennis-Jordan        BQL_LOCK_GUARD();
6672352159cSPhil Dennis-Jordan        set_mode(s, sizeInPixels.x, sizeInPixels.y);
6682352159cSPhil Dennis-Jordan    };
6692352159cSPhil Dennis-Jordan    disp_desc.cursorGlyphHandler = ^(NSBitmapImageRep *glyph,
6702352159cSPhil Dennis-Jordan                                     PGDisplayCoord_t hotspot) {
6712352159cSPhil Dennis-Jordan        AppleGFXSetCursorGlyphJob *job = g_malloc0(sizeof(*job));
6722352159cSPhil Dennis-Jordan        job->s = s;
6732352159cSPhil Dennis-Jordan        job->glyph = glyph;
6742352159cSPhil Dennis-Jordan        job->hotspot = hotspot;
6752352159cSPhil Dennis-Jordan        [glyph retain];
6762352159cSPhil Dennis-Jordan        aio_bh_schedule_oneshot(qemu_get_aio_context(),
6772352159cSPhil Dennis-Jordan                                set_cursor_glyph, job);
6782352159cSPhil Dennis-Jordan    };
6792352159cSPhil Dennis-Jordan    disp_desc.cursorShowHandler = ^(BOOL show) {
6802352159cSPhil Dennis-Jordan        trace_apple_gfx_cursor_show(show);
6812352159cSPhil Dennis-Jordan        qatomic_set(&s->cursor_show, show);
6822352159cSPhil Dennis-Jordan        aio_bh_schedule_oneshot(qemu_get_aio_context(),
6832352159cSPhil Dennis-Jordan                                update_cursor_bh, s);
6842352159cSPhil Dennis-Jordan    };
6852352159cSPhil Dennis-Jordan    disp_desc.cursorMoveHandler = ^(void) {
6862352159cSPhil Dennis-Jordan        trace_apple_gfx_cursor_move();
6872352159cSPhil Dennis-Jordan        aio_bh_schedule_oneshot(qemu_get_aio_context(),
6882352159cSPhil Dennis-Jordan                                update_cursor_bh, s);
6892352159cSPhil Dennis-Jordan    };
6902352159cSPhil Dennis-Jordan
6912352159cSPhil Dennis-Jordan    return disp_desc;
6922352159cSPhil Dennis-Jordan}
6932352159cSPhil Dennis-Jordan
694*bb43a234SPhil Dennis-Jordanstatic NSArray<PGDisplayMode *> *apple_gfx_create_display_mode_array(
695*bb43a234SPhil Dennis-Jordan    const AppleGFXDisplayMode display_modes[], uint32_t display_mode_count)
6962352159cSPhil Dennis-Jordan{
697*bb43a234SPhil Dennis-Jordan    PGDisplayMode *mode_obj;
698*bb43a234SPhil Dennis-Jordan    NSMutableArray<PGDisplayMode *> *mode_array =
699*bb43a234SPhil Dennis-Jordan        [[NSMutableArray alloc] initWithCapacity:display_mode_count];
7002352159cSPhil Dennis-Jordan
701*bb43a234SPhil Dennis-Jordan    for (unsigned i = 0; i < display_mode_count; i++) {
702*bb43a234SPhil Dennis-Jordan        const AppleGFXDisplayMode *mode = &display_modes[i];
703*bb43a234SPhil Dennis-Jordan        trace_apple_gfx_display_mode(i, mode->width_px, mode->height_px);
704*bb43a234SPhil Dennis-Jordan        PGDisplayCoord_t mode_size = { mode->width_px, mode->height_px };
7052352159cSPhil Dennis-Jordan
706*bb43a234SPhil Dennis-Jordan        mode_obj =
707*bb43a234SPhil Dennis-Jordan            [[PGDisplayMode alloc] initWithSizeInPixels:mode_size
708*bb43a234SPhil Dennis-Jordan                                        refreshRateInHz:mode->refresh_rate_hz];
709*bb43a234SPhil Dennis-Jordan        [mode_array addObject:mode_obj];
710*bb43a234SPhil Dennis-Jordan        [mode_obj release];
7112352159cSPhil Dennis-Jordan    }
7122352159cSPhil Dennis-Jordan
7132352159cSPhil Dennis-Jordan    return mode_array;
7142352159cSPhil Dennis-Jordan}
7152352159cSPhil Dennis-Jordan
7162352159cSPhil Dennis-Jordanstatic id<MTLDevice> copy_suitable_metal_device(void)
7172352159cSPhil Dennis-Jordan{
7182352159cSPhil Dennis-Jordan    id<MTLDevice> dev = nil;
7192352159cSPhil Dennis-Jordan    NSArray<id<MTLDevice>> *devs = MTLCopyAllDevices();
7202352159cSPhil Dennis-Jordan
7212352159cSPhil Dennis-Jordan    /* Prefer a unified memory GPU. Failing that, pick a non-removable GPU. */
7222352159cSPhil Dennis-Jordan    for (size_t i = 0; i < devs.count; ++i) {
7232352159cSPhil Dennis-Jordan        if (devs[i].hasUnifiedMemory) {
7242352159cSPhil Dennis-Jordan            dev = devs[i];
7252352159cSPhil Dennis-Jordan            break;
7262352159cSPhil Dennis-Jordan        }
7272352159cSPhil Dennis-Jordan        if (!devs[i].removable) {
7282352159cSPhil Dennis-Jordan            dev = devs[i];
7292352159cSPhil Dennis-Jordan        }
7302352159cSPhil Dennis-Jordan    }
7312352159cSPhil Dennis-Jordan
7322352159cSPhil Dennis-Jordan    if (dev != nil) {
7332352159cSPhil Dennis-Jordan        [dev retain];
7342352159cSPhil Dennis-Jordan    } else {
7352352159cSPhil Dennis-Jordan        dev = MTLCreateSystemDefaultDevice();
7362352159cSPhil Dennis-Jordan    }
7372352159cSPhil Dennis-Jordan    [devs release];
7382352159cSPhil Dennis-Jordan
7392352159cSPhil Dennis-Jordan    return dev;
7402352159cSPhil Dennis-Jordan}
7412352159cSPhil Dennis-Jordan
7422352159cSPhil Dennis-Jordanbool apple_gfx_common_realize(AppleGFXState *s, DeviceState *dev,
7432352159cSPhil Dennis-Jordan                              PGDeviceDescriptor *desc, Error **errp)
7442352159cSPhil Dennis-Jordan{
7452352159cSPhil Dennis-Jordan    PGDisplayDescriptor *disp_desc;
746*bb43a234SPhil Dennis-Jordan    const AppleGFXDisplayMode *display_modes = apple_gfx_default_modes;
747*bb43a234SPhil Dennis-Jordan    uint32_t num_display_modes = ARRAY_SIZE(apple_gfx_default_modes);
748*bb43a234SPhil Dennis-Jordan    NSArray<PGDisplayMode *> *mode_array;
7492352159cSPhil Dennis-Jordan
7502352159cSPhil Dennis-Jordan    if (apple_gfx_mig_blocker == NULL) {
7512352159cSPhil Dennis-Jordan        error_setg(&apple_gfx_mig_blocker,
7522352159cSPhil Dennis-Jordan                  "Migration state blocked by apple-gfx display device");
7532352159cSPhil Dennis-Jordan        if (migrate_add_blocker(&apple_gfx_mig_blocker, errp) < 0) {
7542352159cSPhil Dennis-Jordan            return false;
7552352159cSPhil Dennis-Jordan        }
7562352159cSPhil Dennis-Jordan    }
7572352159cSPhil Dennis-Jordan
7582352159cSPhil Dennis-Jordan    qemu_mutex_init(&s->task_mutex);
7592352159cSPhil Dennis-Jordan    QTAILQ_INIT(&s->tasks);
7602352159cSPhil Dennis-Jordan    s->mtl = copy_suitable_metal_device();
7612352159cSPhil Dennis-Jordan    s->mtl_queue = [s->mtl newCommandQueue];
7622352159cSPhil Dennis-Jordan
7632352159cSPhil Dennis-Jordan    desc.device = s->mtl;
7642352159cSPhil Dennis-Jordan
7652352159cSPhil Dennis-Jordan    apple_gfx_register_task_mapping_handlers(s, desc);
7662352159cSPhil Dennis-Jordan
7672352159cSPhil Dennis-Jordan    s->cursor_show = true;
7682352159cSPhil Dennis-Jordan
7692352159cSPhil Dennis-Jordan    s->pgdev = PGNewDeviceWithDescriptor(desc);
7702352159cSPhil Dennis-Jordan
7712352159cSPhil Dennis-Jordan    disp_desc = apple_gfx_prepare_display_descriptor(s);
7722352159cSPhil Dennis-Jordan    /*
7732352159cSPhil Dennis-Jordan     * Although the framework does, this integration currently does not support
7742352159cSPhil Dennis-Jordan     * multiple virtual displays connected to a single PV graphics device.
7752352159cSPhil Dennis-Jordan     * It is however possible to create
7762352159cSPhil Dennis-Jordan     * more than one instance of the device, each with one display. The macOS
7772352159cSPhil Dennis-Jordan     * guest will ignore these displays if they share the same serial number,
7782352159cSPhil Dennis-Jordan     * so ensure each instance gets a unique one.
7792352159cSPhil Dennis-Jordan     */
7802352159cSPhil Dennis-Jordan    s->pgdisp = [s->pgdev newDisplayWithDescriptor:disp_desc
7812352159cSPhil Dennis-Jordan                                              port:0
7822352159cSPhil Dennis-Jordan                                         serialNum:next_pgdisplay_serial_num++];
7832352159cSPhil Dennis-Jordan    [disp_desc release];
784*bb43a234SPhil Dennis-Jordan
785*bb43a234SPhil Dennis-Jordan    if (s->display_modes != NULL && s->num_display_modes > 0) {
786*bb43a234SPhil Dennis-Jordan        trace_apple_gfx_common_realize_modes_property(s->num_display_modes);
787*bb43a234SPhil Dennis-Jordan        display_modes = s->display_modes;
788*bb43a234SPhil Dennis-Jordan        num_display_modes = s->num_display_modes;
789*bb43a234SPhil Dennis-Jordan    }
790*bb43a234SPhil Dennis-Jordan    s->pgdisp.modeList = mode_array =
791*bb43a234SPhil Dennis-Jordan        apple_gfx_create_display_mode_array(display_modes, num_display_modes);
792*bb43a234SPhil Dennis-Jordan    [mode_array release];
7932352159cSPhil Dennis-Jordan
7942352159cSPhil Dennis-Jordan    s->con = graphic_console_init(dev, 0, &apple_gfx_fb_ops, s);
7952352159cSPhil Dennis-Jordan    return true;
7962352159cSPhil Dennis-Jordan}
797*bb43a234SPhil Dennis-Jordan
798*bb43a234SPhil Dennis-Jordan/* ------ Display mode list device property ------ */
799*bb43a234SPhil Dennis-Jordan
800*bb43a234SPhil Dennis-Jordanstatic void apple_gfx_get_display_mode(Object *obj, Visitor *v,
801*bb43a234SPhil Dennis-Jordan                                       const char *name, void *opaque,
802*bb43a234SPhil Dennis-Jordan                                       Error **errp)
803*bb43a234SPhil Dennis-Jordan{
804*bb43a234SPhil Dennis-Jordan    Property *prop = opaque;
805*bb43a234SPhil Dennis-Jordan    AppleGFXDisplayMode *mode = object_field_prop_ptr(obj, prop);
806*bb43a234SPhil Dennis-Jordan    /* 3 uint16s (max 5 digits) + 2 separator characters + nul. */
807*bb43a234SPhil Dennis-Jordan    char buffer[5 * 3 + 2 + 1];
808*bb43a234SPhil Dennis-Jordan    char *pos = buffer;
809*bb43a234SPhil Dennis-Jordan
810*bb43a234SPhil Dennis-Jordan    int rc = snprintf(buffer, sizeof(buffer),
811*bb43a234SPhil Dennis-Jordan                      "%"PRIu16"x%"PRIu16"@%"PRIu16,
812*bb43a234SPhil Dennis-Jordan                      mode->width_px, mode->height_px,
813*bb43a234SPhil Dennis-Jordan                      mode->refresh_rate_hz);
814*bb43a234SPhil Dennis-Jordan    assert(rc < sizeof(buffer));
815*bb43a234SPhil Dennis-Jordan
816*bb43a234SPhil Dennis-Jordan    visit_type_str(v, name, &pos, errp);
817*bb43a234SPhil Dennis-Jordan}
818*bb43a234SPhil Dennis-Jordan
819*bb43a234SPhil Dennis-Jordanstatic void apple_gfx_set_display_mode(Object *obj, Visitor *v,
820*bb43a234SPhil Dennis-Jordan                                       const char *name, void *opaque,
821*bb43a234SPhil Dennis-Jordan                                       Error **errp)
822*bb43a234SPhil Dennis-Jordan{
823*bb43a234SPhil Dennis-Jordan    Property *prop = opaque;
824*bb43a234SPhil Dennis-Jordan    AppleGFXDisplayMode *mode = object_field_prop_ptr(obj, prop);
825*bb43a234SPhil Dennis-Jordan    const char *endptr;
826*bb43a234SPhil Dennis-Jordan    g_autofree char *str = NULL;
827*bb43a234SPhil Dennis-Jordan    int ret;
828*bb43a234SPhil Dennis-Jordan    int val;
829*bb43a234SPhil Dennis-Jordan
830*bb43a234SPhil Dennis-Jordan    if (!visit_type_str(v, name, &str, errp)) {
831*bb43a234SPhil Dennis-Jordan        return;
832*bb43a234SPhil Dennis-Jordan    }
833*bb43a234SPhil Dennis-Jordan
834*bb43a234SPhil Dennis-Jordan    endptr = str;
835*bb43a234SPhil Dennis-Jordan
836*bb43a234SPhil Dennis-Jordan    ret = qemu_strtoi(endptr, &endptr, 10, &val);
837*bb43a234SPhil Dennis-Jordan    if (ret || val > UINT16_MAX || val <= 0) {
838*bb43a234SPhil Dennis-Jordan        error_setg(errp, "width in '%s' must be a decimal integer number"
839*bb43a234SPhil Dennis-Jordan                         " of pixels in the range 1..65535", name);
840*bb43a234SPhil Dennis-Jordan        return;
841*bb43a234SPhil Dennis-Jordan    }
842*bb43a234SPhil Dennis-Jordan    mode->width_px = val;
843*bb43a234SPhil Dennis-Jordan    if (*endptr != 'x') {
844*bb43a234SPhil Dennis-Jordan        goto separator_error;
845*bb43a234SPhil Dennis-Jordan    }
846*bb43a234SPhil Dennis-Jordan
847*bb43a234SPhil Dennis-Jordan    ret = qemu_strtoi(endptr + 1, &endptr, 10, &val);
848*bb43a234SPhil Dennis-Jordan    if (ret || val > UINT16_MAX || val <= 0) {
849*bb43a234SPhil Dennis-Jordan        error_setg(errp, "height in '%s' must be a decimal integer number"
850*bb43a234SPhil Dennis-Jordan                         " of pixels in the range 1..65535", name);
851*bb43a234SPhil Dennis-Jordan        return;
852*bb43a234SPhil Dennis-Jordan    }
853*bb43a234SPhil Dennis-Jordan    mode->height_px = val;
854*bb43a234SPhil Dennis-Jordan    if (*endptr != '@') {
855*bb43a234SPhil Dennis-Jordan        goto separator_error;
856*bb43a234SPhil Dennis-Jordan    }
857*bb43a234SPhil Dennis-Jordan
858*bb43a234SPhil Dennis-Jordan    ret = qemu_strtoi(endptr + 1, &endptr, 10, &val);
859*bb43a234SPhil Dennis-Jordan    if (ret || val > UINT16_MAX || val <= 0) {
860*bb43a234SPhil Dennis-Jordan        error_setg(errp, "refresh rate in '%s'"
861*bb43a234SPhil Dennis-Jordan                         " must be a positive decimal integer (Hertz)", name);
862*bb43a234SPhil Dennis-Jordan        return;
863*bb43a234SPhil Dennis-Jordan    }
864*bb43a234SPhil Dennis-Jordan    mode->refresh_rate_hz = val;
865*bb43a234SPhil Dennis-Jordan    return;
866*bb43a234SPhil Dennis-Jordan
867*bb43a234SPhil Dennis-Jordanseparator_error:
868*bb43a234SPhil Dennis-Jordan    error_setg(errp,
869*bb43a234SPhil Dennis-Jordan               "Each display mode takes the format '<width>x<height>@<rate>'");
870*bb43a234SPhil Dennis-Jordan}
871*bb43a234SPhil Dennis-Jordan
872*bb43a234SPhil Dennis-Jordanconst PropertyInfo qdev_prop_apple_gfx_display_mode = {
873*bb43a234SPhil Dennis-Jordan    .name  = "display_mode",
874*bb43a234SPhil Dennis-Jordan    .description =
875*bb43a234SPhil Dennis-Jordan        "Display mode in pixels and Hertz, as <width>x<height>@<refresh-rate> "
876*bb43a234SPhil Dennis-Jordan        "Example: 3840x2160@60",
877*bb43a234SPhil Dennis-Jordan    .get   = apple_gfx_get_display_mode,
878*bb43a234SPhil Dennis-Jordan    .set   = apple_gfx_set_display_mode,
879*bb43a234SPhil Dennis-Jordan};
880