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, ®ion); 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