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" 21dfc56946SRichard Henderson#include "system/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 34bb43a234SPhil Dennis-Jordanstatic const AppleGFXDisplayMode apple_gfx_default_modes[] = { 35bb43a234SPhil Dennis-Jordan { 1920, 1080, 60 }, 36bb43a234SPhil Dennis-Jordan { 1440, 1080, 60 }, 37bb43a234SPhil 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 /* 72231a8420SMichael Tokarev * All unique MemoryRegions for which a mapping has been created 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 || 140d732b5a4SDavid Hildenbrand !memory_access_is_direct(ram_region, !read_only, 141d732b5a4SDavid Hildenbrand MEMTXATTRS_UNSPECIFIED)) { 1422352159cSPhil Dennis-Jordan return NULL; 1432352159cSPhil Dennis-Jordan } 1442352159cSPhil Dennis-Jordan 1452352159cSPhil Dennis-Jordan host_ptr = memory_region_get_ram_ptr(ram_region); 1462352159cSPhil Dennis-Jordan if (!host_ptr) { 1472352159cSPhil Dennis-Jordan return NULL; 1482352159cSPhil Dennis-Jordan } 1492352159cSPhil Dennis-Jordan host_ptr += ram_region_offset; 1502352159cSPhil Dennis-Jordan *mapping_in_region = ram_region; 1512352159cSPhil Dennis-Jordan return host_ptr; 1522352159cSPhil Dennis-Jordan} 1532352159cSPhil Dennis-Jordan 1542352159cSPhil Dennis-Jordanstatic bool apple_gfx_task_map_memory(AppleGFXState *s, PGTask_t *task, 1552352159cSPhil Dennis-Jordan uint64_t virtual_offset, 1562352159cSPhil Dennis-Jordan PGPhysicalMemoryRange_t *ranges, 1572352159cSPhil Dennis-Jordan uint32_t range_count, bool read_only) 1582352159cSPhil Dennis-Jordan{ 1592352159cSPhil Dennis-Jordan kern_return_t r; 1602352159cSPhil Dennis-Jordan void *source_ptr; 1612352159cSPhil Dennis-Jordan mach_vm_address_t target; 1622352159cSPhil Dennis-Jordan vm_prot_t cur_protection, max_protection; 1632352159cSPhil Dennis-Jordan bool success = true; 1642352159cSPhil Dennis-Jordan MemoryRegion *region; 1652352159cSPhil Dennis-Jordan 1662352159cSPhil Dennis-Jordan RCU_READ_LOCK_GUARD(); 1672352159cSPhil Dennis-Jordan QEMU_LOCK_GUARD(&s->task_mutex); 1682352159cSPhil Dennis-Jordan 1692352159cSPhil Dennis-Jordan trace_apple_gfx_map_memory(task, range_count, virtual_offset, read_only); 1702352159cSPhil Dennis-Jordan for (int i = 0; i < range_count; i++) { 1712352159cSPhil Dennis-Jordan PGPhysicalMemoryRange_t *range = &ranges[i]; 1722352159cSPhil Dennis-Jordan 1732352159cSPhil Dennis-Jordan target = task->address + virtual_offset; 1742352159cSPhil Dennis-Jordan virtual_offset += range->physicalLength; 1752352159cSPhil Dennis-Jordan 1762352159cSPhil Dennis-Jordan trace_apple_gfx_map_memory_range(i, range->physicalAddress, 1772352159cSPhil Dennis-Jordan range->physicalLength); 1782352159cSPhil Dennis-Jordan 1792352159cSPhil Dennis-Jordan region = NULL; 1802352159cSPhil Dennis-Jordan source_ptr = apple_gfx_host_ptr_for_gpa_range(range->physicalAddress, 1812352159cSPhil Dennis-Jordan range->physicalLength, 1822352159cSPhil Dennis-Jordan read_only, ®ion); 1832352159cSPhil Dennis-Jordan if (!source_ptr) { 1842352159cSPhil Dennis-Jordan success = false; 1852352159cSPhil Dennis-Jordan continue; 1862352159cSPhil Dennis-Jordan } 1872352159cSPhil Dennis-Jordan 1882352159cSPhil Dennis-Jordan if (!g_ptr_array_find(task->mapped_regions, region, NULL)) { 1892352159cSPhil Dennis-Jordan g_ptr_array_add(task->mapped_regions, region); 1902352159cSPhil Dennis-Jordan memory_region_ref(region); 1912352159cSPhil Dennis-Jordan } 1922352159cSPhil Dennis-Jordan 1932352159cSPhil Dennis-Jordan cur_protection = 0; 1942352159cSPhil Dennis-Jordan max_protection = 0; 1952352159cSPhil Dennis-Jordan /* Map guest RAM at range->physicalAddress into PG task memory range */ 1962352159cSPhil Dennis-Jordan r = mach_vm_remap(mach_task_self(), 1972352159cSPhil Dennis-Jordan &target, range->physicalLength, vm_page_size - 1, 1982352159cSPhil Dennis-Jordan VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, 1992352159cSPhil Dennis-Jordan mach_task_self(), (mach_vm_address_t)source_ptr, 2002352159cSPhil Dennis-Jordan false /* shared mapping, no copy */, 2012352159cSPhil Dennis-Jordan &cur_protection, &max_protection, 2022352159cSPhil Dennis-Jordan VM_INHERIT_COPY); 2032352159cSPhil Dennis-Jordan trace_apple_gfx_remap(r, source_ptr, target); 2042352159cSPhil Dennis-Jordan g_assert(r == KERN_SUCCESS); 2052352159cSPhil Dennis-Jordan } 2062352159cSPhil Dennis-Jordan 2072352159cSPhil Dennis-Jordan return success; 2082352159cSPhil Dennis-Jordan} 2092352159cSPhil Dennis-Jordan 2102352159cSPhil Dennis-Jordanstatic void apple_gfx_task_unmap_memory(AppleGFXState *s, PGTask_t *task, 2112352159cSPhil Dennis-Jordan uint64_t virtual_offset, uint64_t length) 2122352159cSPhil Dennis-Jordan{ 2132352159cSPhil Dennis-Jordan kern_return_t r; 2142352159cSPhil Dennis-Jordan mach_vm_address_t range_address; 2152352159cSPhil Dennis-Jordan 2162352159cSPhil Dennis-Jordan trace_apple_gfx_unmap_memory(task, virtual_offset, length); 2172352159cSPhil Dennis-Jordan 2182352159cSPhil Dennis-Jordan /* 2192352159cSPhil Dennis-Jordan * Replace task memory range with fresh 0 pages, undoing the mapping 2202352159cSPhil Dennis-Jordan * from guest RAM. 2212352159cSPhil Dennis-Jordan */ 2222352159cSPhil Dennis-Jordan range_address = task->address + virtual_offset; 2232352159cSPhil Dennis-Jordan r = mach_vm_allocate(mach_task_self(), &range_address, length, 2242352159cSPhil Dennis-Jordan VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE); 2252352159cSPhil Dennis-Jordan g_assert(r == KERN_SUCCESS); 2262352159cSPhil Dennis-Jordan} 2272352159cSPhil Dennis-Jordan 2282352159cSPhil Dennis-Jordan/* ------ Rendering and frame management ------ */ 2292352159cSPhil Dennis-Jordan 2302352159cSPhil Dennis-Jordanstatic void apple_gfx_render_frame_completed_bh(void *opaque); 2312352159cSPhil Dennis-Jordan 2322352159cSPhil Dennis-Jordanstatic void apple_gfx_render_new_frame(AppleGFXState *s) 2332352159cSPhil Dennis-Jordan{ 2342352159cSPhil Dennis-Jordan bool managed_texture = s->using_managed_texture_storage; 2352352159cSPhil Dennis-Jordan uint32_t width = surface_width(s->surface); 2362352159cSPhil Dennis-Jordan uint32_t height = surface_height(s->surface); 2372352159cSPhil Dennis-Jordan MTLRegion region = MTLRegionMake2D(0, 0, width, height); 2382352159cSPhil Dennis-Jordan id<MTLCommandBuffer> command_buffer = [s->mtl_queue commandBuffer]; 2392352159cSPhil Dennis-Jordan id<MTLTexture> texture = s->texture; 2402352159cSPhil Dennis-Jordan 2412352159cSPhil Dennis-Jordan assert(bql_locked()); 2422352159cSPhil Dennis-Jordan [texture retain]; 2432352159cSPhil Dennis-Jordan [command_buffer retain]; 2442352159cSPhil Dennis-Jordan 2452352159cSPhil Dennis-Jordan s->rendering_frame_width = width; 2462352159cSPhil Dennis-Jordan s->rendering_frame_height = height; 2472352159cSPhil Dennis-Jordan 2482352159cSPhil Dennis-Jordan dispatch_async(get_background_queue(), ^{ 2492352159cSPhil Dennis-Jordan /* 2502352159cSPhil Dennis-Jordan * This is not safe to call from the BQL/BH due to PVG-internal locks 2512352159cSPhil Dennis-Jordan * causing deadlocks. 2522352159cSPhil Dennis-Jordan */ 2532352159cSPhil Dennis-Jordan bool r = [s->pgdisp encodeCurrentFrameToCommandBuffer:command_buffer 2542352159cSPhil Dennis-Jordan texture:texture 2552352159cSPhil Dennis-Jordan region:region]; 2562352159cSPhil Dennis-Jordan if (!r) { 2572352159cSPhil Dennis-Jordan [texture release]; 2582352159cSPhil Dennis-Jordan [command_buffer release]; 2592352159cSPhil Dennis-Jordan qemu_log_mask(LOG_GUEST_ERROR, 2602352159cSPhil Dennis-Jordan "%s: encodeCurrentFrameToCommandBuffer:texture:region: " 2612352159cSPhil Dennis-Jordan "failed\n", __func__); 2622352159cSPhil Dennis-Jordan bql_lock(); 2632352159cSPhil Dennis-Jordan --s->pending_frames; 2642352159cSPhil Dennis-Jordan if (s->pending_frames > 0) { 2652352159cSPhil Dennis-Jordan apple_gfx_render_new_frame(s); 2662352159cSPhil Dennis-Jordan } 2672352159cSPhil Dennis-Jordan bql_unlock(); 2682352159cSPhil Dennis-Jordan return; 2692352159cSPhil Dennis-Jordan } 2702352159cSPhil Dennis-Jordan 2712352159cSPhil Dennis-Jordan if (managed_texture) { 2722352159cSPhil Dennis-Jordan /* "Managed" textures exist in both VRAM and RAM and must be synced. */ 2732352159cSPhil Dennis-Jordan id<MTLBlitCommandEncoder> blit = [command_buffer blitCommandEncoder]; 2742352159cSPhil Dennis-Jordan [blit synchronizeResource:texture]; 2752352159cSPhil Dennis-Jordan [blit endEncoding]; 2762352159cSPhil Dennis-Jordan } 2772352159cSPhil Dennis-Jordan [texture release]; 2782352159cSPhil Dennis-Jordan [command_buffer addCompletedHandler: 2792352159cSPhil Dennis-Jordan ^(id<MTLCommandBuffer> cb) 2802352159cSPhil Dennis-Jordan { 2812352159cSPhil Dennis-Jordan aio_bh_schedule_oneshot(qemu_get_aio_context(), 2822352159cSPhil Dennis-Jordan apple_gfx_render_frame_completed_bh, s); 2832352159cSPhil Dennis-Jordan }]; 2842352159cSPhil Dennis-Jordan [command_buffer commit]; 2852352159cSPhil Dennis-Jordan [command_buffer release]; 2862352159cSPhil Dennis-Jordan }); 2872352159cSPhil Dennis-Jordan} 2882352159cSPhil Dennis-Jordan 2892352159cSPhil Dennis-Jordanstatic void copy_mtl_texture_to_surface_mem(id<MTLTexture> texture, void *vram) 2902352159cSPhil Dennis-Jordan{ 2912352159cSPhil Dennis-Jordan /* 2922352159cSPhil Dennis-Jordan * TODO: Skip this entirely on a pure Metal or headless/guest-only 2932352159cSPhil Dennis-Jordan * rendering path, else use a blit command encoder? Needs careful 2942352159cSPhil Dennis-Jordan * (double?) buffering design. 2952352159cSPhil Dennis-Jordan */ 2962352159cSPhil Dennis-Jordan size_t width = texture.width, height = texture.height; 2972352159cSPhil Dennis-Jordan MTLRegion region = MTLRegionMake2D(0, 0, width, height); 2982352159cSPhil Dennis-Jordan [texture getBytes:vram 2992352159cSPhil Dennis-Jordan bytesPerRow:(width * 4) 3002352159cSPhil Dennis-Jordan bytesPerImage:(width * height * 4) 3012352159cSPhil Dennis-Jordan fromRegion:region 3022352159cSPhil Dennis-Jordan mipmapLevel:0 3032352159cSPhil Dennis-Jordan slice:0]; 3042352159cSPhil Dennis-Jordan} 3052352159cSPhil Dennis-Jordan 3062352159cSPhil Dennis-Jordanstatic void apple_gfx_render_frame_completed_bh(void *opaque) 3072352159cSPhil Dennis-Jordan{ 3082352159cSPhil Dennis-Jordan AppleGFXState *s = opaque; 3092352159cSPhil Dennis-Jordan 3102352159cSPhil Dennis-Jordan @autoreleasepool { 3112352159cSPhil Dennis-Jordan --s->pending_frames; 3122352159cSPhil Dennis-Jordan assert(s->pending_frames >= 0); 3132352159cSPhil Dennis-Jordan 3142352159cSPhil Dennis-Jordan /* Only update display if mode hasn't changed since we started rendering. */ 3152352159cSPhil Dennis-Jordan if (s->rendering_frame_width == surface_width(s->surface) && 3162352159cSPhil Dennis-Jordan s->rendering_frame_height == surface_height(s->surface)) { 3172352159cSPhil Dennis-Jordan copy_mtl_texture_to_surface_mem(s->texture, surface_data(s->surface)); 3182352159cSPhil Dennis-Jordan if (s->gfx_update_requested) { 3192352159cSPhil Dennis-Jordan s->gfx_update_requested = false; 3202352159cSPhil Dennis-Jordan dpy_gfx_update_full(s->con); 3212352159cSPhil Dennis-Jordan graphic_hw_update_done(s->con); 3222352159cSPhil Dennis-Jordan s->new_frame_ready = false; 3232352159cSPhil Dennis-Jordan } else { 3242352159cSPhil Dennis-Jordan s->new_frame_ready = true; 3252352159cSPhil Dennis-Jordan } 3262352159cSPhil Dennis-Jordan } 3272352159cSPhil Dennis-Jordan if (s->pending_frames > 0) { 3282352159cSPhil Dennis-Jordan apple_gfx_render_new_frame(s); 3292352159cSPhil Dennis-Jordan } 3302352159cSPhil Dennis-Jordan } 3312352159cSPhil Dennis-Jordan} 3322352159cSPhil Dennis-Jordan 3332352159cSPhil Dennis-Jordanstatic void apple_gfx_fb_update_display(void *opaque) 3342352159cSPhil Dennis-Jordan{ 3352352159cSPhil Dennis-Jordan AppleGFXState *s = opaque; 3362352159cSPhil Dennis-Jordan 3372352159cSPhil Dennis-Jordan assert(bql_locked()); 3382352159cSPhil Dennis-Jordan if (s->new_frame_ready) { 3392352159cSPhil Dennis-Jordan dpy_gfx_update_full(s->con); 3402352159cSPhil Dennis-Jordan s->new_frame_ready = false; 3412352159cSPhil Dennis-Jordan graphic_hw_update_done(s->con); 3422352159cSPhil Dennis-Jordan } else if (s->pending_frames > 0) { 3432352159cSPhil Dennis-Jordan s->gfx_update_requested = true; 3442352159cSPhil Dennis-Jordan } else { 3452352159cSPhil Dennis-Jordan graphic_hw_update_done(s->con); 3462352159cSPhil Dennis-Jordan } 3472352159cSPhil Dennis-Jordan} 3482352159cSPhil Dennis-Jordan 3492352159cSPhil Dennis-Jordanstatic const GraphicHwOps apple_gfx_fb_ops = { 3502352159cSPhil Dennis-Jordan .gfx_update = apple_gfx_fb_update_display, 3512352159cSPhil Dennis-Jordan .gfx_update_async = true, 3522352159cSPhil Dennis-Jordan}; 3532352159cSPhil Dennis-Jordan 3542352159cSPhil Dennis-Jordan/* ------ Mouse cursor and display mode setting ------ */ 3552352159cSPhil Dennis-Jordan 3562352159cSPhil Dennis-Jordanstatic void set_mode(AppleGFXState *s, uint32_t width, uint32_t height) 3572352159cSPhil Dennis-Jordan{ 3582352159cSPhil Dennis-Jordan MTLTextureDescriptor *textureDescriptor; 3592352159cSPhil Dennis-Jordan 3602352159cSPhil Dennis-Jordan if (s->surface && 3612352159cSPhil Dennis-Jordan width == surface_width(s->surface) && 3622352159cSPhil Dennis-Jordan height == surface_height(s->surface)) { 3632352159cSPhil Dennis-Jordan return; 3642352159cSPhil Dennis-Jordan } 3652352159cSPhil Dennis-Jordan 3662352159cSPhil Dennis-Jordan [s->texture release]; 3672352159cSPhil Dennis-Jordan 3682352159cSPhil Dennis-Jordan s->surface = qemu_create_displaysurface(width, height); 3692352159cSPhil Dennis-Jordan 3702352159cSPhil Dennis-Jordan @autoreleasepool { 3712352159cSPhil Dennis-Jordan textureDescriptor = 3722352159cSPhil Dennis-Jordan [MTLTextureDescriptor 3732352159cSPhil Dennis-Jordan texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 3742352159cSPhil Dennis-Jordan width:width 3752352159cSPhil Dennis-Jordan height:height 3762352159cSPhil Dennis-Jordan mipmapped:NO]; 3772352159cSPhil Dennis-Jordan textureDescriptor.usage = s->pgdisp.minimumTextureUsage; 3782352159cSPhil Dennis-Jordan s->texture = [s->mtl newTextureWithDescriptor:textureDescriptor]; 3792352159cSPhil Dennis-Jordan s->using_managed_texture_storage = 3802352159cSPhil Dennis-Jordan (s->texture.storageMode == MTLStorageModeManaged); 3812352159cSPhil Dennis-Jordan } 3822352159cSPhil Dennis-Jordan 3832352159cSPhil Dennis-Jordan dpy_gfx_replace_surface(s->con, s->surface); 3842352159cSPhil Dennis-Jordan} 3852352159cSPhil Dennis-Jordan 3862352159cSPhil Dennis-Jordanstatic void update_cursor(AppleGFXState *s) 3872352159cSPhil Dennis-Jordan{ 3882352159cSPhil Dennis-Jordan assert(bql_locked()); 3892352159cSPhil Dennis-Jordan dpy_mouse_set(s->con, s->pgdisp.cursorPosition.x, 3902352159cSPhil Dennis-Jordan s->pgdisp.cursorPosition.y, qatomic_read(&s->cursor_show)); 3912352159cSPhil Dennis-Jordan} 3922352159cSPhil Dennis-Jordan 3932352159cSPhil Dennis-Jordanstatic void update_cursor_bh(void *opaque) 3942352159cSPhil Dennis-Jordan{ 3952352159cSPhil Dennis-Jordan AppleGFXState *s = opaque; 3962352159cSPhil Dennis-Jordan update_cursor(s); 3972352159cSPhil Dennis-Jordan} 3982352159cSPhil Dennis-Jordan 3992352159cSPhil Dennis-Jordantypedef struct AppleGFXSetCursorGlyphJob { 4002352159cSPhil Dennis-Jordan AppleGFXState *s; 4012352159cSPhil Dennis-Jordan NSBitmapImageRep *glyph; 4022352159cSPhil Dennis-Jordan PGDisplayCoord_t hotspot; 4032352159cSPhil Dennis-Jordan} AppleGFXSetCursorGlyphJob; 4042352159cSPhil Dennis-Jordan 4052352159cSPhil Dennis-Jordanstatic void set_cursor_glyph(void *opaque) 4062352159cSPhil Dennis-Jordan{ 4072352159cSPhil Dennis-Jordan AppleGFXSetCursorGlyphJob *job = opaque; 4082352159cSPhil Dennis-Jordan AppleGFXState *s = job->s; 4092352159cSPhil Dennis-Jordan NSBitmapImageRep *glyph = job->glyph; 4102352159cSPhil Dennis-Jordan uint32_t bpp = glyph.bitsPerPixel; 4112352159cSPhil Dennis-Jordan size_t width = glyph.pixelsWide; 4122352159cSPhil Dennis-Jordan size_t height = glyph.pixelsHigh; 4132352159cSPhil Dennis-Jordan size_t padding_bytes_per_row = glyph.bytesPerRow - width * 4; 4142352159cSPhil Dennis-Jordan const uint8_t* px_data = glyph.bitmapData; 4152352159cSPhil Dennis-Jordan 4162352159cSPhil Dennis-Jordan trace_apple_gfx_cursor_set(bpp, width, height); 4172352159cSPhil Dennis-Jordan 4182352159cSPhil Dennis-Jordan if (s->cursor) { 4192352159cSPhil Dennis-Jordan cursor_unref(s->cursor); 4202352159cSPhil Dennis-Jordan s->cursor = NULL; 4212352159cSPhil Dennis-Jordan } 4222352159cSPhil Dennis-Jordan 4232352159cSPhil Dennis-Jordan if (bpp == 32) { /* Shouldn't be anything else, but just to be safe... */ 4242352159cSPhil Dennis-Jordan s->cursor = cursor_alloc(width, height); 4252352159cSPhil Dennis-Jordan s->cursor->hot_x = job->hotspot.x; 4262352159cSPhil Dennis-Jordan s->cursor->hot_y = job->hotspot.y; 4272352159cSPhil Dennis-Jordan 4282352159cSPhil Dennis-Jordan uint32_t *dest_px = s->cursor->data; 4292352159cSPhil Dennis-Jordan 4302352159cSPhil Dennis-Jordan for (size_t y = 0; y < height; ++y) { 4312352159cSPhil Dennis-Jordan for (size_t x = 0; x < width; ++x) { 4322352159cSPhil Dennis-Jordan /* 4332352159cSPhil Dennis-Jordan * NSBitmapImageRep's red & blue channels are swapped 4342352159cSPhil Dennis-Jordan * compared to QEMUCursor's. 4352352159cSPhil Dennis-Jordan */ 4362352159cSPhil Dennis-Jordan *dest_px = 4372352159cSPhil Dennis-Jordan (px_data[0] << 16u) | 4382352159cSPhil Dennis-Jordan (px_data[1] << 8u) | 4392352159cSPhil Dennis-Jordan (px_data[2] << 0u) | 4402352159cSPhil Dennis-Jordan (px_data[3] << 24u); 4412352159cSPhil Dennis-Jordan ++dest_px; 4422352159cSPhil Dennis-Jordan px_data += 4; 4432352159cSPhil Dennis-Jordan } 4442352159cSPhil Dennis-Jordan px_data += padding_bytes_per_row; 4452352159cSPhil Dennis-Jordan } 4462352159cSPhil Dennis-Jordan dpy_cursor_define(s->con, s->cursor); 4472352159cSPhil Dennis-Jordan update_cursor(s); 4482352159cSPhil Dennis-Jordan } 4492352159cSPhil Dennis-Jordan [glyph release]; 4502352159cSPhil Dennis-Jordan 4512352159cSPhil Dennis-Jordan g_free(job); 4522352159cSPhil Dennis-Jordan} 4532352159cSPhil Dennis-Jordan 4542352159cSPhil Dennis-Jordan/* ------ DMA (device reading system memory) ------ */ 4552352159cSPhil Dennis-Jordan 4562352159cSPhil Dennis-Jordantypedef struct AppleGFXReadMemoryJob { 457*23aec0d0SAkihiko Odaki QemuEvent event; 4582352159cSPhil Dennis-Jordan hwaddr physical_address; 4592352159cSPhil Dennis-Jordan uint64_t length; 4602352159cSPhil Dennis-Jordan void *dst; 4612352159cSPhil Dennis-Jordan bool success; 4622352159cSPhil Dennis-Jordan} AppleGFXReadMemoryJob; 4632352159cSPhil Dennis-Jordan 4642352159cSPhil Dennis-Jordanstatic void apple_gfx_do_read_memory(void *opaque) 4652352159cSPhil Dennis-Jordan{ 4662352159cSPhil Dennis-Jordan AppleGFXReadMemoryJob *job = opaque; 4672352159cSPhil Dennis-Jordan MemTxResult r; 4682352159cSPhil Dennis-Jordan 4692352159cSPhil Dennis-Jordan r = dma_memory_read(&address_space_memory, job->physical_address, 4702352159cSPhil Dennis-Jordan job->dst, job->length, MEMTXATTRS_UNSPECIFIED); 4712352159cSPhil Dennis-Jordan job->success = (r == MEMTX_OK); 4722352159cSPhil Dennis-Jordan 473*23aec0d0SAkihiko Odaki qemu_event_set(&job->event); 4742352159cSPhil Dennis-Jordan} 4752352159cSPhil Dennis-Jordan 4762352159cSPhil Dennis-Jordanstatic bool apple_gfx_read_memory(AppleGFXState *s, hwaddr physical_address, 4772352159cSPhil Dennis-Jordan uint64_t length, void *dst) 4782352159cSPhil Dennis-Jordan{ 4792352159cSPhil Dennis-Jordan AppleGFXReadMemoryJob job = { 4802352159cSPhil Dennis-Jordan .physical_address = physical_address, .length = length, .dst = dst 4812352159cSPhil Dennis-Jordan }; 4822352159cSPhil Dennis-Jordan 4832352159cSPhil Dennis-Jordan trace_apple_gfx_read_memory(physical_address, length, dst); 4842352159cSPhil Dennis-Jordan 4852352159cSPhil Dennis-Jordan /* Performing DMA requires BQL, so do it in a BH. */ 486*23aec0d0SAkihiko Odaki qemu_event_init(&job.event, 0); 4872352159cSPhil Dennis-Jordan aio_bh_schedule_oneshot(qemu_get_aio_context(), 4882352159cSPhil Dennis-Jordan apple_gfx_do_read_memory, &job); 489*23aec0d0SAkihiko Odaki qemu_event_wait(&job.event); 490*23aec0d0SAkihiko Odaki qemu_event_destroy(&job.event); 4912352159cSPhil Dennis-Jordan return job.success; 4922352159cSPhil Dennis-Jordan} 4932352159cSPhil Dennis-Jordan 4942352159cSPhil Dennis-Jordan/* ------ Memory-mapped device I/O operations ------ */ 4952352159cSPhil Dennis-Jordan 4962352159cSPhil Dennis-Jordantypedef struct AppleGFXIOJob { 4972352159cSPhil Dennis-Jordan AppleGFXState *state; 4982352159cSPhil Dennis-Jordan uint64_t offset; 4992352159cSPhil Dennis-Jordan uint64_t value; 5002352159cSPhil Dennis-Jordan bool completed; 5012352159cSPhil Dennis-Jordan} AppleGFXIOJob; 5022352159cSPhil Dennis-Jordan 5032352159cSPhil Dennis-Jordanstatic void apple_gfx_do_read(void *opaque) 5042352159cSPhil Dennis-Jordan{ 5052352159cSPhil Dennis-Jordan AppleGFXIOJob *job = opaque; 5062352159cSPhil Dennis-Jordan job->value = [job->state->pgdev mmioReadAtOffset:job->offset]; 5072352159cSPhil Dennis-Jordan qatomic_set(&job->completed, true); 5082352159cSPhil Dennis-Jordan aio_wait_kick(); 5092352159cSPhil Dennis-Jordan} 5102352159cSPhil Dennis-Jordan 5112352159cSPhil Dennis-Jordanstatic uint64_t apple_gfx_read(void *opaque, hwaddr offset, unsigned size) 5122352159cSPhil Dennis-Jordan{ 5132352159cSPhil Dennis-Jordan AppleGFXIOJob job = { 5142352159cSPhil Dennis-Jordan .state = opaque, 5152352159cSPhil Dennis-Jordan .offset = offset, 5162352159cSPhil Dennis-Jordan .completed = false, 5172352159cSPhil Dennis-Jordan }; 5182352159cSPhil Dennis-Jordan dispatch_queue_t queue = get_background_queue(); 5192352159cSPhil Dennis-Jordan 5202352159cSPhil Dennis-Jordan dispatch_async_f(queue, &job, apple_gfx_do_read); 5212352159cSPhil Dennis-Jordan AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed)); 5222352159cSPhil Dennis-Jordan 5232352159cSPhil Dennis-Jordan trace_apple_gfx_read(offset, job.value); 5242352159cSPhil Dennis-Jordan return job.value; 5252352159cSPhil Dennis-Jordan} 5262352159cSPhil Dennis-Jordan 5272352159cSPhil Dennis-Jordanstatic void apple_gfx_do_write(void *opaque) 5282352159cSPhil Dennis-Jordan{ 5292352159cSPhil Dennis-Jordan AppleGFXIOJob *job = opaque; 5302352159cSPhil Dennis-Jordan [job->state->pgdev mmioWriteAtOffset:job->offset value:job->value]; 5312352159cSPhil Dennis-Jordan qatomic_set(&job->completed, true); 5322352159cSPhil Dennis-Jordan aio_wait_kick(); 5332352159cSPhil Dennis-Jordan} 5342352159cSPhil Dennis-Jordan 5352352159cSPhil Dennis-Jordanstatic void apple_gfx_write(void *opaque, hwaddr offset, uint64_t val, 5362352159cSPhil Dennis-Jordan unsigned size) 5372352159cSPhil Dennis-Jordan{ 5382352159cSPhil Dennis-Jordan /* 5392352159cSPhil Dennis-Jordan * The methods mmioReadAtOffset: and especially mmioWriteAtOffset: can 5402352159cSPhil Dennis-Jordan * trigger synchronous operations on other dispatch queues, which in turn 5412352159cSPhil Dennis-Jordan * may call back out on one or more of the callback blocks. For this reason, 5422352159cSPhil Dennis-Jordan * and as we are holding the BQL, we invoke the I/O methods on a pool 5432352159cSPhil Dennis-Jordan * thread and handle AIO tasks while we wait. Any work in the callbacks 5442352159cSPhil Dennis-Jordan * requiring the BQL will in turn schedule BHs which this thread will 5452352159cSPhil Dennis-Jordan * process while waiting. 5462352159cSPhil Dennis-Jordan */ 5472352159cSPhil Dennis-Jordan AppleGFXIOJob job = { 5482352159cSPhil Dennis-Jordan .state = opaque, 5492352159cSPhil Dennis-Jordan .offset = offset, 5502352159cSPhil Dennis-Jordan .value = val, 5512352159cSPhil Dennis-Jordan .completed = false, 5522352159cSPhil Dennis-Jordan }; 5532352159cSPhil Dennis-Jordan dispatch_queue_t queue = get_background_queue(); 5542352159cSPhil Dennis-Jordan 5552352159cSPhil Dennis-Jordan dispatch_async_f(queue, &job, apple_gfx_do_write); 5562352159cSPhil Dennis-Jordan AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed)); 5572352159cSPhil Dennis-Jordan 5582352159cSPhil Dennis-Jordan trace_apple_gfx_write(offset, val); 5592352159cSPhil Dennis-Jordan} 5602352159cSPhil Dennis-Jordan 5612352159cSPhil Dennis-Jordanstatic const MemoryRegionOps apple_gfx_ops = { 5622352159cSPhil Dennis-Jordan .read = apple_gfx_read, 5632352159cSPhil Dennis-Jordan .write = apple_gfx_write, 5642352159cSPhil Dennis-Jordan .endianness = DEVICE_LITTLE_ENDIAN, 5652352159cSPhil Dennis-Jordan .valid = { 5662352159cSPhil Dennis-Jordan .min_access_size = 4, 5672352159cSPhil Dennis-Jordan .max_access_size = 8, 5682352159cSPhil Dennis-Jordan }, 5692352159cSPhil Dennis-Jordan .impl = { 5702352159cSPhil Dennis-Jordan .min_access_size = 4, 5712352159cSPhil Dennis-Jordan .max_access_size = 4, 5722352159cSPhil Dennis-Jordan }, 5732352159cSPhil Dennis-Jordan}; 5742352159cSPhil Dennis-Jordan 5752352159cSPhil Dennis-Jordanstatic size_t apple_gfx_get_default_mmio_range_size(void) 5762352159cSPhil Dennis-Jordan{ 5772352159cSPhil Dennis-Jordan size_t mmio_range_size; 5782352159cSPhil Dennis-Jordan @autoreleasepool { 5792352159cSPhil Dennis-Jordan PGDeviceDescriptor *desc = [PGDeviceDescriptor new]; 5802352159cSPhil Dennis-Jordan mmio_range_size = desc.mmioLength; 5812352159cSPhil Dennis-Jordan [desc release]; 5822352159cSPhil Dennis-Jordan } 5832352159cSPhil Dennis-Jordan return mmio_range_size; 5842352159cSPhil Dennis-Jordan} 5852352159cSPhil Dennis-Jordan 5862352159cSPhil Dennis-Jordan/* ------ Initialisation and startup ------ */ 5872352159cSPhil Dennis-Jordan 5882352159cSPhil Dennis-Jordanvoid apple_gfx_common_init(Object *obj, AppleGFXState *s, const char* obj_name) 5892352159cSPhil Dennis-Jordan{ 5902352159cSPhil Dennis-Jordan size_t mmio_range_size = apple_gfx_get_default_mmio_range_size(); 5912352159cSPhil Dennis-Jordan 5922352159cSPhil Dennis-Jordan trace_apple_gfx_common_init(obj_name, mmio_range_size); 5932352159cSPhil Dennis-Jordan memory_region_init_io(&s->iomem_gfx, obj, &apple_gfx_ops, s, obj_name, 5942352159cSPhil Dennis-Jordan mmio_range_size); 5952352159cSPhil Dennis-Jordan 5962352159cSPhil Dennis-Jordan /* TODO: PVG framework supports serialising device state: integrate it! */ 5972352159cSPhil Dennis-Jordan} 5982352159cSPhil Dennis-Jordan 5992352159cSPhil Dennis-Jordanstatic void apple_gfx_register_task_mapping_handlers(AppleGFXState *s, 6002352159cSPhil Dennis-Jordan PGDeviceDescriptor *desc) 6012352159cSPhil Dennis-Jordan{ 6022352159cSPhil Dennis-Jordan desc.createTask = ^(uint64_t vmSize, void * _Nullable * _Nonnull baseAddress) { 6032352159cSPhil Dennis-Jordan PGTask_t *task = apple_gfx_new_task(s, vmSize); 6042352159cSPhil Dennis-Jordan *baseAddress = (void *)task->address; 6052352159cSPhil Dennis-Jordan trace_apple_gfx_create_task(vmSize, *baseAddress); 6062352159cSPhil Dennis-Jordan return task; 6072352159cSPhil Dennis-Jordan }; 6082352159cSPhil Dennis-Jordan 6092352159cSPhil Dennis-Jordan desc.destroyTask = ^(PGTask_t * _Nonnull task) { 6102352159cSPhil Dennis-Jordan trace_apple_gfx_destroy_task(task, task->mapped_regions->len); 6112352159cSPhil Dennis-Jordan 6122352159cSPhil Dennis-Jordan apple_gfx_destroy_task(s, task); 6132352159cSPhil Dennis-Jordan }; 6142352159cSPhil Dennis-Jordan 6152352159cSPhil Dennis-Jordan desc.mapMemory = ^bool(PGTask_t * _Nonnull task, uint32_t range_count, 6162352159cSPhil Dennis-Jordan uint64_t virtual_offset, bool read_only, 6172352159cSPhil Dennis-Jordan PGPhysicalMemoryRange_t * _Nonnull ranges) { 6182352159cSPhil Dennis-Jordan return apple_gfx_task_map_memory(s, task, virtual_offset, 6192352159cSPhil Dennis-Jordan ranges, range_count, read_only); 6202352159cSPhil Dennis-Jordan }; 6212352159cSPhil Dennis-Jordan 6222352159cSPhil Dennis-Jordan desc.unmapMemory = ^bool(PGTask_t * _Nonnull task, uint64_t virtual_offset, 6232352159cSPhil Dennis-Jordan uint64_t length) { 6242352159cSPhil Dennis-Jordan apple_gfx_task_unmap_memory(s, task, virtual_offset, length); 6252352159cSPhil Dennis-Jordan return true; 6262352159cSPhil Dennis-Jordan }; 6272352159cSPhil Dennis-Jordan 6282352159cSPhil Dennis-Jordan desc.readMemory = ^bool(uint64_t physical_address, uint64_t length, 6292352159cSPhil Dennis-Jordan void * _Nonnull dst) { 6302352159cSPhil Dennis-Jordan return apple_gfx_read_memory(s, physical_address, length, dst); 6312352159cSPhil Dennis-Jordan }; 6322352159cSPhil Dennis-Jordan} 6332352159cSPhil Dennis-Jordan 6342352159cSPhil Dennis-Jordanstatic void new_frame_handler_bh(void *opaque) 6352352159cSPhil Dennis-Jordan{ 6362352159cSPhil Dennis-Jordan AppleGFXState *s = opaque; 6372352159cSPhil Dennis-Jordan 6382352159cSPhil Dennis-Jordan /* Drop frames if guest gets too far ahead. */ 6392352159cSPhil Dennis-Jordan if (s->pending_frames >= 2) { 6402352159cSPhil Dennis-Jordan return; 6412352159cSPhil Dennis-Jordan } 6422352159cSPhil Dennis-Jordan ++s->pending_frames; 6432352159cSPhil Dennis-Jordan if (s->pending_frames > 1) { 6442352159cSPhil Dennis-Jordan return; 6452352159cSPhil Dennis-Jordan } 6462352159cSPhil Dennis-Jordan 6472352159cSPhil Dennis-Jordan @autoreleasepool { 6482352159cSPhil Dennis-Jordan apple_gfx_render_new_frame(s); 6492352159cSPhil Dennis-Jordan } 6502352159cSPhil Dennis-Jordan} 6512352159cSPhil Dennis-Jordan 6522352159cSPhil Dennis-Jordanstatic PGDisplayDescriptor *apple_gfx_prepare_display_descriptor(AppleGFXState *s) 6532352159cSPhil Dennis-Jordan{ 6542352159cSPhil Dennis-Jordan PGDisplayDescriptor *disp_desc = [PGDisplayDescriptor new]; 6552352159cSPhil Dennis-Jordan 6562352159cSPhil Dennis-Jordan disp_desc.name = @"QEMU display"; 6572352159cSPhil Dennis-Jordan disp_desc.sizeInMillimeters = NSMakeSize(400., 300.); /* A 20" display */ 6582352159cSPhil Dennis-Jordan disp_desc.queue = dispatch_get_main_queue(); 6592352159cSPhil Dennis-Jordan disp_desc.newFrameEventHandler = ^(void) { 6602352159cSPhil Dennis-Jordan trace_apple_gfx_new_frame(); 6612352159cSPhil Dennis-Jordan aio_bh_schedule_oneshot(qemu_get_aio_context(), new_frame_handler_bh, s); 6622352159cSPhil Dennis-Jordan }; 6632352159cSPhil Dennis-Jordan disp_desc.modeChangeHandler = ^(PGDisplayCoord_t sizeInPixels, 6642352159cSPhil Dennis-Jordan OSType pixelFormat) { 6652352159cSPhil Dennis-Jordan trace_apple_gfx_mode_change(sizeInPixels.x, sizeInPixels.y); 6662352159cSPhil Dennis-Jordan 6672352159cSPhil Dennis-Jordan BQL_LOCK_GUARD(); 6682352159cSPhil Dennis-Jordan set_mode(s, sizeInPixels.x, sizeInPixels.y); 6692352159cSPhil Dennis-Jordan }; 6702352159cSPhil Dennis-Jordan disp_desc.cursorGlyphHandler = ^(NSBitmapImageRep *glyph, 6712352159cSPhil Dennis-Jordan PGDisplayCoord_t hotspot) { 6722352159cSPhil Dennis-Jordan AppleGFXSetCursorGlyphJob *job = g_malloc0(sizeof(*job)); 6732352159cSPhil Dennis-Jordan job->s = s; 6742352159cSPhil Dennis-Jordan job->glyph = glyph; 6752352159cSPhil Dennis-Jordan job->hotspot = hotspot; 6762352159cSPhil Dennis-Jordan [glyph retain]; 6772352159cSPhil Dennis-Jordan aio_bh_schedule_oneshot(qemu_get_aio_context(), 6782352159cSPhil Dennis-Jordan set_cursor_glyph, job); 6792352159cSPhil Dennis-Jordan }; 6802352159cSPhil Dennis-Jordan disp_desc.cursorShowHandler = ^(BOOL show) { 6812352159cSPhil Dennis-Jordan trace_apple_gfx_cursor_show(show); 6822352159cSPhil Dennis-Jordan qatomic_set(&s->cursor_show, show); 6832352159cSPhil Dennis-Jordan aio_bh_schedule_oneshot(qemu_get_aio_context(), 6842352159cSPhil Dennis-Jordan update_cursor_bh, s); 6852352159cSPhil Dennis-Jordan }; 6862352159cSPhil Dennis-Jordan disp_desc.cursorMoveHandler = ^(void) { 6872352159cSPhil Dennis-Jordan trace_apple_gfx_cursor_move(); 6882352159cSPhil Dennis-Jordan aio_bh_schedule_oneshot(qemu_get_aio_context(), 6892352159cSPhil Dennis-Jordan update_cursor_bh, s); 6902352159cSPhil Dennis-Jordan }; 6912352159cSPhil Dennis-Jordan 6922352159cSPhil Dennis-Jordan return disp_desc; 6932352159cSPhil Dennis-Jordan} 6942352159cSPhil Dennis-Jordan 695bb43a234SPhil Dennis-Jordanstatic NSArray<PGDisplayMode *> *apple_gfx_create_display_mode_array( 696bb43a234SPhil Dennis-Jordan const AppleGFXDisplayMode display_modes[], uint32_t display_mode_count) 6972352159cSPhil Dennis-Jordan{ 698bb43a234SPhil Dennis-Jordan PGDisplayMode *mode_obj; 699bb43a234SPhil Dennis-Jordan NSMutableArray<PGDisplayMode *> *mode_array = 700bb43a234SPhil Dennis-Jordan [[NSMutableArray alloc] initWithCapacity:display_mode_count]; 7012352159cSPhil Dennis-Jordan 702bb43a234SPhil Dennis-Jordan for (unsigned i = 0; i < display_mode_count; i++) { 703bb43a234SPhil Dennis-Jordan const AppleGFXDisplayMode *mode = &display_modes[i]; 704bb43a234SPhil Dennis-Jordan trace_apple_gfx_display_mode(i, mode->width_px, mode->height_px); 705bb43a234SPhil Dennis-Jordan PGDisplayCoord_t mode_size = { mode->width_px, mode->height_px }; 7062352159cSPhil Dennis-Jordan 707bb43a234SPhil Dennis-Jordan mode_obj = 708bb43a234SPhil Dennis-Jordan [[PGDisplayMode alloc] initWithSizeInPixels:mode_size 709bb43a234SPhil Dennis-Jordan refreshRateInHz:mode->refresh_rate_hz]; 710bb43a234SPhil Dennis-Jordan [mode_array addObject:mode_obj]; 711bb43a234SPhil Dennis-Jordan [mode_obj release]; 7122352159cSPhil Dennis-Jordan } 7132352159cSPhil Dennis-Jordan 7142352159cSPhil Dennis-Jordan return mode_array; 7152352159cSPhil Dennis-Jordan} 7162352159cSPhil Dennis-Jordan 7172352159cSPhil Dennis-Jordanstatic id<MTLDevice> copy_suitable_metal_device(void) 7182352159cSPhil Dennis-Jordan{ 7192352159cSPhil Dennis-Jordan id<MTLDevice> dev = nil; 7202352159cSPhil Dennis-Jordan NSArray<id<MTLDevice>> *devs = MTLCopyAllDevices(); 7212352159cSPhil Dennis-Jordan 7222352159cSPhil Dennis-Jordan /* Prefer a unified memory GPU. Failing that, pick a non-removable GPU. */ 7232352159cSPhil Dennis-Jordan for (size_t i = 0; i < devs.count; ++i) { 7242352159cSPhil Dennis-Jordan if (devs[i].hasUnifiedMemory) { 7252352159cSPhil Dennis-Jordan dev = devs[i]; 7262352159cSPhil Dennis-Jordan break; 7272352159cSPhil Dennis-Jordan } 7282352159cSPhil Dennis-Jordan if (!devs[i].removable) { 7292352159cSPhil Dennis-Jordan dev = devs[i]; 7302352159cSPhil Dennis-Jordan } 7312352159cSPhil Dennis-Jordan } 7322352159cSPhil Dennis-Jordan 7332352159cSPhil Dennis-Jordan if (dev != nil) { 7342352159cSPhil Dennis-Jordan [dev retain]; 7352352159cSPhil Dennis-Jordan } else { 7362352159cSPhil Dennis-Jordan dev = MTLCreateSystemDefaultDevice(); 7372352159cSPhil Dennis-Jordan } 7382352159cSPhil Dennis-Jordan [devs release]; 7392352159cSPhil Dennis-Jordan 7402352159cSPhil Dennis-Jordan return dev; 7412352159cSPhil Dennis-Jordan} 7422352159cSPhil Dennis-Jordan 7432352159cSPhil Dennis-Jordanbool apple_gfx_common_realize(AppleGFXState *s, DeviceState *dev, 7442352159cSPhil Dennis-Jordan PGDeviceDescriptor *desc, Error **errp) 7452352159cSPhil Dennis-Jordan{ 7462352159cSPhil Dennis-Jordan PGDisplayDescriptor *disp_desc; 747bb43a234SPhil Dennis-Jordan const AppleGFXDisplayMode *display_modes = apple_gfx_default_modes; 748bb43a234SPhil Dennis-Jordan uint32_t num_display_modes = ARRAY_SIZE(apple_gfx_default_modes); 749bb43a234SPhil Dennis-Jordan NSArray<PGDisplayMode *> *mode_array; 7502352159cSPhil Dennis-Jordan 7512352159cSPhil Dennis-Jordan if (apple_gfx_mig_blocker == NULL) { 7522352159cSPhil Dennis-Jordan error_setg(&apple_gfx_mig_blocker, 7532352159cSPhil Dennis-Jordan "Migration state blocked by apple-gfx display device"); 7542352159cSPhil Dennis-Jordan if (migrate_add_blocker(&apple_gfx_mig_blocker, errp) < 0) { 7552352159cSPhil Dennis-Jordan return false; 7562352159cSPhil Dennis-Jordan } 7572352159cSPhil Dennis-Jordan } 7582352159cSPhil Dennis-Jordan 7592352159cSPhil Dennis-Jordan qemu_mutex_init(&s->task_mutex); 7602352159cSPhil Dennis-Jordan QTAILQ_INIT(&s->tasks); 7612352159cSPhil Dennis-Jordan s->mtl = copy_suitable_metal_device(); 7622352159cSPhil Dennis-Jordan s->mtl_queue = [s->mtl newCommandQueue]; 7632352159cSPhil Dennis-Jordan 7642352159cSPhil Dennis-Jordan desc.device = s->mtl; 7652352159cSPhil Dennis-Jordan 7662352159cSPhil Dennis-Jordan apple_gfx_register_task_mapping_handlers(s, desc); 7672352159cSPhil Dennis-Jordan 7682352159cSPhil Dennis-Jordan s->cursor_show = true; 7692352159cSPhil Dennis-Jordan 7702352159cSPhil Dennis-Jordan s->pgdev = PGNewDeviceWithDescriptor(desc); 7712352159cSPhil Dennis-Jordan 7722352159cSPhil Dennis-Jordan disp_desc = apple_gfx_prepare_display_descriptor(s); 7732352159cSPhil Dennis-Jordan /* 7742352159cSPhil Dennis-Jordan * Although the framework does, this integration currently does not support 7752352159cSPhil Dennis-Jordan * multiple virtual displays connected to a single PV graphics device. 7762352159cSPhil Dennis-Jordan * It is however possible to create 7772352159cSPhil Dennis-Jordan * more than one instance of the device, each with one display. The macOS 7782352159cSPhil Dennis-Jordan * guest will ignore these displays if they share the same serial number, 7792352159cSPhil Dennis-Jordan * so ensure each instance gets a unique one. 7802352159cSPhil Dennis-Jordan */ 7812352159cSPhil Dennis-Jordan s->pgdisp = [s->pgdev newDisplayWithDescriptor:disp_desc 7822352159cSPhil Dennis-Jordan port:0 7832352159cSPhil Dennis-Jordan serialNum:next_pgdisplay_serial_num++]; 7842352159cSPhil Dennis-Jordan [disp_desc release]; 785bb43a234SPhil Dennis-Jordan 786bb43a234SPhil Dennis-Jordan if (s->display_modes != NULL && s->num_display_modes > 0) { 787bb43a234SPhil Dennis-Jordan trace_apple_gfx_common_realize_modes_property(s->num_display_modes); 788bb43a234SPhil Dennis-Jordan display_modes = s->display_modes; 789bb43a234SPhil Dennis-Jordan num_display_modes = s->num_display_modes; 790bb43a234SPhil Dennis-Jordan } 791bb43a234SPhil Dennis-Jordan s->pgdisp.modeList = mode_array = 792bb43a234SPhil Dennis-Jordan apple_gfx_create_display_mode_array(display_modes, num_display_modes); 793bb43a234SPhil Dennis-Jordan [mode_array release]; 7942352159cSPhil Dennis-Jordan 7952352159cSPhil Dennis-Jordan s->con = graphic_console_init(dev, 0, &apple_gfx_fb_ops, s); 7962352159cSPhil Dennis-Jordan return true; 7972352159cSPhil Dennis-Jordan} 798bb43a234SPhil Dennis-Jordan 799bb43a234SPhil Dennis-Jordan/* ------ Display mode list device property ------ */ 800bb43a234SPhil Dennis-Jordan 801bb43a234SPhil Dennis-Jordanstatic void apple_gfx_get_display_mode(Object *obj, Visitor *v, 802bb43a234SPhil Dennis-Jordan const char *name, void *opaque, 803bb43a234SPhil Dennis-Jordan Error **errp) 804bb43a234SPhil Dennis-Jordan{ 805bb43a234SPhil Dennis-Jordan Property *prop = opaque; 806bb43a234SPhil Dennis-Jordan AppleGFXDisplayMode *mode = object_field_prop_ptr(obj, prop); 807bb43a234SPhil Dennis-Jordan /* 3 uint16s (max 5 digits) + 2 separator characters + nul. */ 808bb43a234SPhil Dennis-Jordan char buffer[5 * 3 + 2 + 1]; 809bb43a234SPhil Dennis-Jordan char *pos = buffer; 810bb43a234SPhil Dennis-Jordan 811bb43a234SPhil Dennis-Jordan int rc = snprintf(buffer, sizeof(buffer), 812bb43a234SPhil Dennis-Jordan "%"PRIu16"x%"PRIu16"@%"PRIu16, 813bb43a234SPhil Dennis-Jordan mode->width_px, mode->height_px, 814bb43a234SPhil Dennis-Jordan mode->refresh_rate_hz); 815bb43a234SPhil Dennis-Jordan assert(rc < sizeof(buffer)); 816bb43a234SPhil Dennis-Jordan 817bb43a234SPhil Dennis-Jordan visit_type_str(v, name, &pos, errp); 818bb43a234SPhil Dennis-Jordan} 819bb43a234SPhil Dennis-Jordan 820bb43a234SPhil Dennis-Jordanstatic void apple_gfx_set_display_mode(Object *obj, Visitor *v, 821bb43a234SPhil Dennis-Jordan const char *name, void *opaque, 822bb43a234SPhil Dennis-Jordan Error **errp) 823bb43a234SPhil Dennis-Jordan{ 824bb43a234SPhil Dennis-Jordan Property *prop = opaque; 825bb43a234SPhil Dennis-Jordan AppleGFXDisplayMode *mode = object_field_prop_ptr(obj, prop); 826bb43a234SPhil Dennis-Jordan const char *endptr; 827bb43a234SPhil Dennis-Jordan g_autofree char *str = NULL; 828bb43a234SPhil Dennis-Jordan int ret; 829bb43a234SPhil Dennis-Jordan int val; 830bb43a234SPhil Dennis-Jordan 831bb43a234SPhil Dennis-Jordan if (!visit_type_str(v, name, &str, errp)) { 832bb43a234SPhil Dennis-Jordan return; 833bb43a234SPhil Dennis-Jordan } 834bb43a234SPhil Dennis-Jordan 835bb43a234SPhil Dennis-Jordan endptr = str; 836bb43a234SPhil Dennis-Jordan 837bb43a234SPhil Dennis-Jordan ret = qemu_strtoi(endptr, &endptr, 10, &val); 838bb43a234SPhil Dennis-Jordan if (ret || val > UINT16_MAX || val <= 0) { 839bb43a234SPhil Dennis-Jordan error_setg(errp, "width in '%s' must be a decimal integer number" 840bb43a234SPhil Dennis-Jordan " of pixels in the range 1..65535", name); 841bb43a234SPhil Dennis-Jordan return; 842bb43a234SPhil Dennis-Jordan } 843bb43a234SPhil Dennis-Jordan mode->width_px = val; 844bb43a234SPhil Dennis-Jordan if (*endptr != 'x') { 845bb43a234SPhil Dennis-Jordan goto separator_error; 846bb43a234SPhil Dennis-Jordan } 847bb43a234SPhil Dennis-Jordan 848bb43a234SPhil Dennis-Jordan ret = qemu_strtoi(endptr + 1, &endptr, 10, &val); 849bb43a234SPhil Dennis-Jordan if (ret || val > UINT16_MAX || val <= 0) { 850bb43a234SPhil Dennis-Jordan error_setg(errp, "height in '%s' must be a decimal integer number" 851bb43a234SPhil Dennis-Jordan " of pixels in the range 1..65535", name); 852bb43a234SPhil Dennis-Jordan return; 853bb43a234SPhil Dennis-Jordan } 854bb43a234SPhil Dennis-Jordan mode->height_px = val; 855bb43a234SPhil Dennis-Jordan if (*endptr != '@') { 856bb43a234SPhil Dennis-Jordan goto separator_error; 857bb43a234SPhil Dennis-Jordan } 858bb43a234SPhil Dennis-Jordan 859bb43a234SPhil Dennis-Jordan ret = qemu_strtoi(endptr + 1, &endptr, 10, &val); 860bb43a234SPhil Dennis-Jordan if (ret || val > UINT16_MAX || val <= 0) { 861bb43a234SPhil Dennis-Jordan error_setg(errp, "refresh rate in '%s'" 862bb43a234SPhil Dennis-Jordan " must be a positive decimal integer (Hertz)", name); 863bb43a234SPhil Dennis-Jordan return; 864bb43a234SPhil Dennis-Jordan } 865bb43a234SPhil Dennis-Jordan mode->refresh_rate_hz = val; 866bb43a234SPhil Dennis-Jordan return; 867bb43a234SPhil Dennis-Jordan 868bb43a234SPhil Dennis-Jordanseparator_error: 869bb43a234SPhil Dennis-Jordan error_setg(errp, 870bb43a234SPhil Dennis-Jordan "Each display mode takes the format '<width>x<height>@<rate>'"); 871bb43a234SPhil Dennis-Jordan} 872bb43a234SPhil Dennis-Jordan 873bb43a234SPhil Dennis-Jordanconst PropertyInfo qdev_prop_apple_gfx_display_mode = { 874c98dac16SMarkus Armbruster .type = "display_mode", 875bb43a234SPhil Dennis-Jordan .description = 876bb43a234SPhil Dennis-Jordan "Display mode in pixels and Hertz, as <width>x<height>@<refresh-rate> " 877bb43a234SPhil Dennis-Jordan "Example: 3840x2160@60", 878bb43a234SPhil Dennis-Jordan .get = apple_gfx_get_display_mode, 879bb43a234SPhil Dennis-Jordan .set = apple_gfx_set_display_mode, 880bb43a234SPhil Dennis-Jordan}; 881