123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880 |
- /*
- * QEMU Apple ParavirtualizedGraphics.framework device
- *
- * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- *
- * ParavirtualizedGraphics.framework is a set of libraries that macOS provides
- * which implements 3d graphics passthrough to the host as well as a
- * proprietary guest communication channel to drive it. This device model
- * implements support to drive that library from within QEMU.
- */
- #include "qemu/osdep.h"
- #include "qemu/lockable.h"
- #include "qemu/cutils.h"
- #include "qemu/log.h"
- #include "qapi/visitor.h"
- #include "qapi/error.h"
- #include "block/aio-wait.h"
- #include "exec/address-spaces.h"
- #include "system/dma.h"
- #include "migration/blocker.h"
- #include "ui/console.h"
- #include "apple-gfx.h"
- #include "trace.h"
- #include <mach/mach.h>
- #include <mach/mach_vm.h>
- #include <dispatch/dispatch.h>
- #import <ParavirtualizedGraphics/ParavirtualizedGraphics.h>
- static const AppleGFXDisplayMode apple_gfx_default_modes[] = {
- { 1920, 1080, 60 },
- { 1440, 1080, 60 },
- { 1280, 1024, 60 },
- };
- static Error *apple_gfx_mig_blocker;
- static uint32_t next_pgdisplay_serial_num = 1;
- static dispatch_queue_t get_background_queue(void)
- {
- return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- }
- /* ------ PGTask and task operations: new/destroy/map/unmap ------ */
- /*
- * This implements the type declared in <ParavirtualizedGraphics/PGDevice.h>
- * which is opaque from the framework's point of view. It is used in callbacks
- * in the form of its typedef PGTask_t, which also already exists in the
- * framework headers.
- *
- * A "task" in PVG terminology represents a host-virtual contiguous address
- * range which is reserved in a large chunk on task creation. The mapMemory
- * callback then requests ranges of guest system memory (identified by their
- * GPA) to be mapped into subranges of this reserved address space.
- * This type of operation isn't well-supported by QEMU's memory subsystem,
- * but it is fortunately trivial to achieve with Darwin's mach_vm_remap() call,
- * which allows us to refer to the same backing memory via multiple virtual
- * address ranges. The Mach VM APIs are therefore used throughout for managing
- * task memory.
- */
- struct PGTask_s {
- QTAILQ_ENTRY(PGTask_s) node;
- AppleGFXState *s;
- mach_vm_address_t address;
- uint64_t len;
- /*
- * All unique MemoryRegions for which a mapping has been created in in this
- * task, and on which we have thus called memory_region_ref(). There are
- * usually very few regions of system RAM in total, so we expect this array
- * to be very short. Therefore, no need for sorting or fancy search
- * algorithms, linear search will do.
- * Protected by AppleGFXState's task_mutex.
- */
- GPtrArray *mapped_regions;
- };
- static PGTask_t *apple_gfx_new_task(AppleGFXState *s, uint64_t len)
- {
- mach_vm_address_t task_mem;
- PGTask_t *task;
- kern_return_t r;
- r = mach_vm_allocate(mach_task_self(), &task_mem, len, VM_FLAGS_ANYWHERE);
- if (r != KERN_SUCCESS) {
- return NULL;
- }
- task = g_new0(PGTask_t, 1);
- task->s = s;
- task->address = task_mem;
- task->len = len;
- task->mapped_regions = g_ptr_array_sized_new(2 /* Usually enough */);
- QEMU_LOCK_GUARD(&s->task_mutex);
- QTAILQ_INSERT_TAIL(&s->tasks, task, node);
- return task;
- }
- static void apple_gfx_destroy_task(AppleGFXState *s, PGTask_t *task)
- {
- GPtrArray *regions = task->mapped_regions;
- MemoryRegion *region;
- size_t i;
- for (i = 0; i < regions->len; ++i) {
- region = g_ptr_array_index(regions, i);
- memory_region_unref(region);
- }
- g_ptr_array_unref(regions);
- mach_vm_deallocate(mach_task_self(), task->address, task->len);
- QEMU_LOCK_GUARD(&s->task_mutex);
- QTAILQ_REMOVE(&s->tasks, task, node);
- g_free(task);
- }
- void *apple_gfx_host_ptr_for_gpa_range(uint64_t guest_physical,
- uint64_t length, bool read_only,
- MemoryRegion **mapping_in_region)
- {
- MemoryRegion *ram_region;
- char *host_ptr;
- hwaddr ram_region_offset = 0;
- hwaddr ram_region_length = length;
- ram_region = address_space_translate(&address_space_memory,
- guest_physical,
- &ram_region_offset,
- &ram_region_length, !read_only,
- MEMTXATTRS_UNSPECIFIED);
- if (!ram_region || ram_region_length < length ||
- !memory_access_is_direct(ram_region, !read_only,
- MEMTXATTRS_UNSPECIFIED)) {
- return NULL;
- }
- host_ptr = memory_region_get_ram_ptr(ram_region);
- if (!host_ptr) {
- return NULL;
- }
- host_ptr += ram_region_offset;
- *mapping_in_region = ram_region;
- return host_ptr;
- }
- static bool apple_gfx_task_map_memory(AppleGFXState *s, PGTask_t *task,
- uint64_t virtual_offset,
- PGPhysicalMemoryRange_t *ranges,
- uint32_t range_count, bool read_only)
- {
- kern_return_t r;
- void *source_ptr;
- mach_vm_address_t target;
- vm_prot_t cur_protection, max_protection;
- bool success = true;
- MemoryRegion *region;
- RCU_READ_LOCK_GUARD();
- QEMU_LOCK_GUARD(&s->task_mutex);
- trace_apple_gfx_map_memory(task, range_count, virtual_offset, read_only);
- for (int i = 0; i < range_count; i++) {
- PGPhysicalMemoryRange_t *range = &ranges[i];
- target = task->address + virtual_offset;
- virtual_offset += range->physicalLength;
- trace_apple_gfx_map_memory_range(i, range->physicalAddress,
- range->physicalLength);
- region = NULL;
- source_ptr = apple_gfx_host_ptr_for_gpa_range(range->physicalAddress,
- range->physicalLength,
- read_only, ®ion);
- if (!source_ptr) {
- success = false;
- continue;
- }
- if (!g_ptr_array_find(task->mapped_regions, region, NULL)) {
- g_ptr_array_add(task->mapped_regions, region);
- memory_region_ref(region);
- }
- cur_protection = 0;
- max_protection = 0;
- /* Map guest RAM at range->physicalAddress into PG task memory range */
- r = mach_vm_remap(mach_task_self(),
- &target, range->physicalLength, vm_page_size - 1,
- VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
- mach_task_self(), (mach_vm_address_t)source_ptr,
- false /* shared mapping, no copy */,
- &cur_protection, &max_protection,
- VM_INHERIT_COPY);
- trace_apple_gfx_remap(r, source_ptr, target);
- g_assert(r == KERN_SUCCESS);
- }
- return success;
- }
- static void apple_gfx_task_unmap_memory(AppleGFXState *s, PGTask_t *task,
- uint64_t virtual_offset, uint64_t length)
- {
- kern_return_t r;
- mach_vm_address_t range_address;
- trace_apple_gfx_unmap_memory(task, virtual_offset, length);
- /*
- * Replace task memory range with fresh 0 pages, undoing the mapping
- * from guest RAM.
- */
- range_address = task->address + virtual_offset;
- r = mach_vm_allocate(mach_task_self(), &range_address, length,
- VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE);
- g_assert(r == KERN_SUCCESS);
- }
- /* ------ Rendering and frame management ------ */
- static void apple_gfx_render_frame_completed_bh(void *opaque);
- static void apple_gfx_render_new_frame(AppleGFXState *s)
- {
- bool managed_texture = s->using_managed_texture_storage;
- uint32_t width = surface_width(s->surface);
- uint32_t height = surface_height(s->surface);
- MTLRegion region = MTLRegionMake2D(0, 0, width, height);
- id<MTLCommandBuffer> command_buffer = [s->mtl_queue commandBuffer];
- id<MTLTexture> texture = s->texture;
- assert(bql_locked());
- [texture retain];
- [command_buffer retain];
- s->rendering_frame_width = width;
- s->rendering_frame_height = height;
- dispatch_async(get_background_queue(), ^{
- /*
- * This is not safe to call from the BQL/BH due to PVG-internal locks
- * causing deadlocks.
- */
- bool r = [s->pgdisp encodeCurrentFrameToCommandBuffer:command_buffer
- texture:texture
- region:region];
- if (!r) {
- [texture release];
- [command_buffer release];
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: encodeCurrentFrameToCommandBuffer:texture:region: "
- "failed\n", __func__);
- bql_lock();
- --s->pending_frames;
- if (s->pending_frames > 0) {
- apple_gfx_render_new_frame(s);
- }
- bql_unlock();
- return;
- }
- if (managed_texture) {
- /* "Managed" textures exist in both VRAM and RAM and must be synced. */
- id<MTLBlitCommandEncoder> blit = [command_buffer blitCommandEncoder];
- [blit synchronizeResource:texture];
- [blit endEncoding];
- }
- [texture release];
- [command_buffer addCompletedHandler:
- ^(id<MTLCommandBuffer> cb)
- {
- aio_bh_schedule_oneshot(qemu_get_aio_context(),
- apple_gfx_render_frame_completed_bh, s);
- }];
- [command_buffer commit];
- [command_buffer release];
- });
- }
- static void copy_mtl_texture_to_surface_mem(id<MTLTexture> texture, void *vram)
- {
- /*
- * TODO: Skip this entirely on a pure Metal or headless/guest-only
- * rendering path, else use a blit command encoder? Needs careful
- * (double?) buffering design.
- */
- size_t width = texture.width, height = texture.height;
- MTLRegion region = MTLRegionMake2D(0, 0, width, height);
- [texture getBytes:vram
- bytesPerRow:(width * 4)
- bytesPerImage:(width * height * 4)
- fromRegion:region
- mipmapLevel:0
- slice:0];
- }
- static void apple_gfx_render_frame_completed_bh(void *opaque)
- {
- AppleGFXState *s = opaque;
- @autoreleasepool {
- --s->pending_frames;
- assert(s->pending_frames >= 0);
- /* Only update display if mode hasn't changed since we started rendering. */
- if (s->rendering_frame_width == surface_width(s->surface) &&
- s->rendering_frame_height == surface_height(s->surface)) {
- copy_mtl_texture_to_surface_mem(s->texture, surface_data(s->surface));
- if (s->gfx_update_requested) {
- s->gfx_update_requested = false;
- dpy_gfx_update_full(s->con);
- graphic_hw_update_done(s->con);
- s->new_frame_ready = false;
- } else {
- s->new_frame_ready = true;
- }
- }
- if (s->pending_frames > 0) {
- apple_gfx_render_new_frame(s);
- }
- }
- }
- static void apple_gfx_fb_update_display(void *opaque)
- {
- AppleGFXState *s = opaque;
- assert(bql_locked());
- if (s->new_frame_ready) {
- dpy_gfx_update_full(s->con);
- s->new_frame_ready = false;
- graphic_hw_update_done(s->con);
- } else if (s->pending_frames > 0) {
- s->gfx_update_requested = true;
- } else {
- graphic_hw_update_done(s->con);
- }
- }
- static const GraphicHwOps apple_gfx_fb_ops = {
- .gfx_update = apple_gfx_fb_update_display,
- .gfx_update_async = true,
- };
- /* ------ Mouse cursor and display mode setting ------ */
- static void set_mode(AppleGFXState *s, uint32_t width, uint32_t height)
- {
- MTLTextureDescriptor *textureDescriptor;
- if (s->surface &&
- width == surface_width(s->surface) &&
- height == surface_height(s->surface)) {
- return;
- }
- [s->texture release];
- s->surface = qemu_create_displaysurface(width, height);
- @autoreleasepool {
- textureDescriptor =
- [MTLTextureDescriptor
- texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
- width:width
- height:height
- mipmapped:NO];
- textureDescriptor.usage = s->pgdisp.minimumTextureUsage;
- s->texture = [s->mtl newTextureWithDescriptor:textureDescriptor];
- s->using_managed_texture_storage =
- (s->texture.storageMode == MTLStorageModeManaged);
- }
- dpy_gfx_replace_surface(s->con, s->surface);
- }
- static void update_cursor(AppleGFXState *s)
- {
- assert(bql_locked());
- dpy_mouse_set(s->con, s->pgdisp.cursorPosition.x,
- s->pgdisp.cursorPosition.y, qatomic_read(&s->cursor_show));
- }
- static void update_cursor_bh(void *opaque)
- {
- AppleGFXState *s = opaque;
- update_cursor(s);
- }
- typedef struct AppleGFXSetCursorGlyphJob {
- AppleGFXState *s;
- NSBitmapImageRep *glyph;
- PGDisplayCoord_t hotspot;
- } AppleGFXSetCursorGlyphJob;
- static void set_cursor_glyph(void *opaque)
- {
- AppleGFXSetCursorGlyphJob *job = opaque;
- AppleGFXState *s = job->s;
- NSBitmapImageRep *glyph = job->glyph;
- uint32_t bpp = glyph.bitsPerPixel;
- size_t width = glyph.pixelsWide;
- size_t height = glyph.pixelsHigh;
- size_t padding_bytes_per_row = glyph.bytesPerRow - width * 4;
- const uint8_t* px_data = glyph.bitmapData;
- trace_apple_gfx_cursor_set(bpp, width, height);
- if (s->cursor) {
- cursor_unref(s->cursor);
- s->cursor = NULL;
- }
- if (bpp == 32) { /* Shouldn't be anything else, but just to be safe... */
- s->cursor = cursor_alloc(width, height);
- s->cursor->hot_x = job->hotspot.x;
- s->cursor->hot_y = job->hotspot.y;
- uint32_t *dest_px = s->cursor->data;
- for (size_t y = 0; y < height; ++y) {
- for (size_t x = 0; x < width; ++x) {
- /*
- * NSBitmapImageRep's red & blue channels are swapped
- * compared to QEMUCursor's.
- */
- *dest_px =
- (px_data[0] << 16u) |
- (px_data[1] << 8u) |
- (px_data[2] << 0u) |
- (px_data[3] << 24u);
- ++dest_px;
- px_data += 4;
- }
- px_data += padding_bytes_per_row;
- }
- dpy_cursor_define(s->con, s->cursor);
- update_cursor(s);
- }
- [glyph release];
- g_free(job);
- }
- /* ------ DMA (device reading system memory) ------ */
- typedef struct AppleGFXReadMemoryJob {
- QemuSemaphore sem;
- hwaddr physical_address;
- uint64_t length;
- void *dst;
- bool success;
- } AppleGFXReadMemoryJob;
- static void apple_gfx_do_read_memory(void *opaque)
- {
- AppleGFXReadMemoryJob *job = opaque;
- MemTxResult r;
- r = dma_memory_read(&address_space_memory, job->physical_address,
- job->dst, job->length, MEMTXATTRS_UNSPECIFIED);
- job->success = (r == MEMTX_OK);
- qemu_sem_post(&job->sem);
- }
- static bool apple_gfx_read_memory(AppleGFXState *s, hwaddr physical_address,
- uint64_t length, void *dst)
- {
- AppleGFXReadMemoryJob job = {
- .physical_address = physical_address, .length = length, .dst = dst
- };
- trace_apple_gfx_read_memory(physical_address, length, dst);
- /* Performing DMA requires BQL, so do it in a BH. */
- qemu_sem_init(&job.sem, 0);
- aio_bh_schedule_oneshot(qemu_get_aio_context(),
- apple_gfx_do_read_memory, &job);
- qemu_sem_wait(&job.sem);
- qemu_sem_destroy(&job.sem);
- return job.success;
- }
- /* ------ Memory-mapped device I/O operations ------ */
- typedef struct AppleGFXIOJob {
- AppleGFXState *state;
- uint64_t offset;
- uint64_t value;
- bool completed;
- } AppleGFXIOJob;
- static void apple_gfx_do_read(void *opaque)
- {
- AppleGFXIOJob *job = opaque;
- job->value = [job->state->pgdev mmioReadAtOffset:job->offset];
- qatomic_set(&job->completed, true);
- aio_wait_kick();
- }
- static uint64_t apple_gfx_read(void *opaque, hwaddr offset, unsigned size)
- {
- AppleGFXIOJob job = {
- .state = opaque,
- .offset = offset,
- .completed = false,
- };
- dispatch_queue_t queue = get_background_queue();
- dispatch_async_f(queue, &job, apple_gfx_do_read);
- AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
- trace_apple_gfx_read(offset, job.value);
- return job.value;
- }
- static void apple_gfx_do_write(void *opaque)
- {
- AppleGFXIOJob *job = opaque;
- [job->state->pgdev mmioWriteAtOffset:job->offset value:job->value];
- qatomic_set(&job->completed, true);
- aio_wait_kick();
- }
- static void apple_gfx_write(void *opaque, hwaddr offset, uint64_t val,
- unsigned size)
- {
- /*
- * The methods mmioReadAtOffset: and especially mmioWriteAtOffset: can
- * trigger synchronous operations on other dispatch queues, which in turn
- * may call back out on one or more of the callback blocks. For this reason,
- * and as we are holding the BQL, we invoke the I/O methods on a pool
- * thread and handle AIO tasks while we wait. Any work in the callbacks
- * requiring the BQL will in turn schedule BHs which this thread will
- * process while waiting.
- */
- AppleGFXIOJob job = {
- .state = opaque,
- .offset = offset,
- .value = val,
- .completed = false,
- };
- dispatch_queue_t queue = get_background_queue();
- dispatch_async_f(queue, &job, apple_gfx_do_write);
- AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
- trace_apple_gfx_write(offset, val);
- }
- static const MemoryRegionOps apple_gfx_ops = {
- .read = apple_gfx_read,
- .write = apple_gfx_write,
- .endianness = DEVICE_LITTLE_ENDIAN,
- .valid = {
- .min_access_size = 4,
- .max_access_size = 8,
- },
- .impl = {
- .min_access_size = 4,
- .max_access_size = 4,
- },
- };
- static size_t apple_gfx_get_default_mmio_range_size(void)
- {
- size_t mmio_range_size;
- @autoreleasepool {
- PGDeviceDescriptor *desc = [PGDeviceDescriptor new];
- mmio_range_size = desc.mmioLength;
- [desc release];
- }
- return mmio_range_size;
- }
- /* ------ Initialisation and startup ------ */
- void apple_gfx_common_init(Object *obj, AppleGFXState *s, const char* obj_name)
- {
- size_t mmio_range_size = apple_gfx_get_default_mmio_range_size();
- trace_apple_gfx_common_init(obj_name, mmio_range_size);
- memory_region_init_io(&s->iomem_gfx, obj, &apple_gfx_ops, s, obj_name,
- mmio_range_size);
- /* TODO: PVG framework supports serialising device state: integrate it! */
- }
- static void apple_gfx_register_task_mapping_handlers(AppleGFXState *s,
- PGDeviceDescriptor *desc)
- {
- desc.createTask = ^(uint64_t vmSize, void * _Nullable * _Nonnull baseAddress) {
- PGTask_t *task = apple_gfx_new_task(s, vmSize);
- *baseAddress = (void *)task->address;
- trace_apple_gfx_create_task(vmSize, *baseAddress);
- return task;
- };
- desc.destroyTask = ^(PGTask_t * _Nonnull task) {
- trace_apple_gfx_destroy_task(task, task->mapped_regions->len);
- apple_gfx_destroy_task(s, task);
- };
- desc.mapMemory = ^bool(PGTask_t * _Nonnull task, uint32_t range_count,
- uint64_t virtual_offset, bool read_only,
- PGPhysicalMemoryRange_t * _Nonnull ranges) {
- return apple_gfx_task_map_memory(s, task, virtual_offset,
- ranges, range_count, read_only);
- };
- desc.unmapMemory = ^bool(PGTask_t * _Nonnull task, uint64_t virtual_offset,
- uint64_t length) {
- apple_gfx_task_unmap_memory(s, task, virtual_offset, length);
- return true;
- };
- desc.readMemory = ^bool(uint64_t physical_address, uint64_t length,
- void * _Nonnull dst) {
- return apple_gfx_read_memory(s, physical_address, length, dst);
- };
- }
- static void new_frame_handler_bh(void *opaque)
- {
- AppleGFXState *s = opaque;
- /* Drop frames if guest gets too far ahead. */
- if (s->pending_frames >= 2) {
- return;
- }
- ++s->pending_frames;
- if (s->pending_frames > 1) {
- return;
- }
- @autoreleasepool {
- apple_gfx_render_new_frame(s);
- }
- }
- static PGDisplayDescriptor *apple_gfx_prepare_display_descriptor(AppleGFXState *s)
- {
- PGDisplayDescriptor *disp_desc = [PGDisplayDescriptor new];
- disp_desc.name = @"QEMU display";
- disp_desc.sizeInMillimeters = NSMakeSize(400., 300.); /* A 20" display */
- disp_desc.queue = dispatch_get_main_queue();
- disp_desc.newFrameEventHandler = ^(void) {
- trace_apple_gfx_new_frame();
- aio_bh_schedule_oneshot(qemu_get_aio_context(), new_frame_handler_bh, s);
- };
- disp_desc.modeChangeHandler = ^(PGDisplayCoord_t sizeInPixels,
- OSType pixelFormat) {
- trace_apple_gfx_mode_change(sizeInPixels.x, sizeInPixels.y);
- BQL_LOCK_GUARD();
- set_mode(s, sizeInPixels.x, sizeInPixels.y);
- };
- disp_desc.cursorGlyphHandler = ^(NSBitmapImageRep *glyph,
- PGDisplayCoord_t hotspot) {
- AppleGFXSetCursorGlyphJob *job = g_malloc0(sizeof(*job));
- job->s = s;
- job->glyph = glyph;
- job->hotspot = hotspot;
- [glyph retain];
- aio_bh_schedule_oneshot(qemu_get_aio_context(),
- set_cursor_glyph, job);
- };
- disp_desc.cursorShowHandler = ^(BOOL show) {
- trace_apple_gfx_cursor_show(show);
- qatomic_set(&s->cursor_show, show);
- aio_bh_schedule_oneshot(qemu_get_aio_context(),
- update_cursor_bh, s);
- };
- disp_desc.cursorMoveHandler = ^(void) {
- trace_apple_gfx_cursor_move();
- aio_bh_schedule_oneshot(qemu_get_aio_context(),
- update_cursor_bh, s);
- };
- return disp_desc;
- }
- static NSArray<PGDisplayMode *> *apple_gfx_create_display_mode_array(
- const AppleGFXDisplayMode display_modes[], uint32_t display_mode_count)
- {
- PGDisplayMode *mode_obj;
- NSMutableArray<PGDisplayMode *> *mode_array =
- [[NSMutableArray alloc] initWithCapacity:display_mode_count];
- for (unsigned i = 0; i < display_mode_count; i++) {
- const AppleGFXDisplayMode *mode = &display_modes[i];
- trace_apple_gfx_display_mode(i, mode->width_px, mode->height_px);
- PGDisplayCoord_t mode_size = { mode->width_px, mode->height_px };
- mode_obj =
- [[PGDisplayMode alloc] initWithSizeInPixels:mode_size
- refreshRateInHz:mode->refresh_rate_hz];
- [mode_array addObject:mode_obj];
- [mode_obj release];
- }
- return mode_array;
- }
- static id<MTLDevice> copy_suitable_metal_device(void)
- {
- id<MTLDevice> dev = nil;
- NSArray<id<MTLDevice>> *devs = MTLCopyAllDevices();
- /* Prefer a unified memory GPU. Failing that, pick a non-removable GPU. */
- for (size_t i = 0; i < devs.count; ++i) {
- if (devs[i].hasUnifiedMemory) {
- dev = devs[i];
- break;
- }
- if (!devs[i].removable) {
- dev = devs[i];
- }
- }
- if (dev != nil) {
- [dev retain];
- } else {
- dev = MTLCreateSystemDefaultDevice();
- }
- [devs release];
- return dev;
- }
- bool apple_gfx_common_realize(AppleGFXState *s, DeviceState *dev,
- PGDeviceDescriptor *desc, Error **errp)
- {
- PGDisplayDescriptor *disp_desc;
- const AppleGFXDisplayMode *display_modes = apple_gfx_default_modes;
- uint32_t num_display_modes = ARRAY_SIZE(apple_gfx_default_modes);
- NSArray<PGDisplayMode *> *mode_array;
- if (apple_gfx_mig_blocker == NULL) {
- error_setg(&apple_gfx_mig_blocker,
- "Migration state blocked by apple-gfx display device");
- if (migrate_add_blocker(&apple_gfx_mig_blocker, errp) < 0) {
- return false;
- }
- }
- qemu_mutex_init(&s->task_mutex);
- QTAILQ_INIT(&s->tasks);
- s->mtl = copy_suitable_metal_device();
- s->mtl_queue = [s->mtl newCommandQueue];
- desc.device = s->mtl;
- apple_gfx_register_task_mapping_handlers(s, desc);
- s->cursor_show = true;
- s->pgdev = PGNewDeviceWithDescriptor(desc);
- disp_desc = apple_gfx_prepare_display_descriptor(s);
- /*
- * Although the framework does, this integration currently does not support
- * multiple virtual displays connected to a single PV graphics device.
- * It is however possible to create
- * more than one instance of the device, each with one display. The macOS
- * guest will ignore these displays if they share the same serial number,
- * so ensure each instance gets a unique one.
- */
- s->pgdisp = [s->pgdev newDisplayWithDescriptor:disp_desc
- port:0
- serialNum:next_pgdisplay_serial_num++];
- [disp_desc release];
- if (s->display_modes != NULL && s->num_display_modes > 0) {
- trace_apple_gfx_common_realize_modes_property(s->num_display_modes);
- display_modes = s->display_modes;
- num_display_modes = s->num_display_modes;
- }
- s->pgdisp.modeList = mode_array =
- apple_gfx_create_display_mode_array(display_modes, num_display_modes);
- [mode_array release];
- s->con = graphic_console_init(dev, 0, &apple_gfx_fb_ops, s);
- return true;
- }
- /* ------ Display mode list device property ------ */
- static void apple_gfx_get_display_mode(Object *obj, Visitor *v,
- const char *name, void *opaque,
- Error **errp)
- {
- Property *prop = opaque;
- AppleGFXDisplayMode *mode = object_field_prop_ptr(obj, prop);
- /* 3 uint16s (max 5 digits) + 2 separator characters + nul. */
- char buffer[5 * 3 + 2 + 1];
- char *pos = buffer;
- int rc = snprintf(buffer, sizeof(buffer),
- "%"PRIu16"x%"PRIu16"@%"PRIu16,
- mode->width_px, mode->height_px,
- mode->refresh_rate_hz);
- assert(rc < sizeof(buffer));
- visit_type_str(v, name, &pos, errp);
- }
- static void apple_gfx_set_display_mode(Object *obj, Visitor *v,
- const char *name, void *opaque,
- Error **errp)
- {
- Property *prop = opaque;
- AppleGFXDisplayMode *mode = object_field_prop_ptr(obj, prop);
- const char *endptr;
- g_autofree char *str = NULL;
- int ret;
- int val;
- if (!visit_type_str(v, name, &str, errp)) {
- return;
- }
- endptr = str;
- ret = qemu_strtoi(endptr, &endptr, 10, &val);
- if (ret || val > UINT16_MAX || val <= 0) {
- error_setg(errp, "width in '%s' must be a decimal integer number"
- " of pixels in the range 1..65535", name);
- return;
- }
- mode->width_px = val;
- if (*endptr != 'x') {
- goto separator_error;
- }
- ret = qemu_strtoi(endptr + 1, &endptr, 10, &val);
- if (ret || val > UINT16_MAX || val <= 0) {
- error_setg(errp, "height in '%s' must be a decimal integer number"
- " of pixels in the range 1..65535", name);
- return;
- }
- mode->height_px = val;
- if (*endptr != '@') {
- goto separator_error;
- }
- ret = qemu_strtoi(endptr + 1, &endptr, 10, &val);
- if (ret || val > UINT16_MAX || val <= 0) {
- error_setg(errp, "refresh rate in '%s'"
- " must be a positive decimal integer (Hertz)", name);
- return;
- }
- mode->refresh_rate_hz = val;
- return;
- separator_error:
- error_setg(errp,
- "Each display mode takes the format '<width>x<height>@<rate>'");
- }
- const PropertyInfo qdev_prop_apple_gfx_display_mode = {
- .type = "display_mode",
- .description =
- "Display mode in pixels and Hertz, as <width>x<height>@<refresh-rate> "
- "Example: 3840x2160@60",
- .get = apple_gfx_get_display_mode,
- .set = apple_gfx_set_display_mode,
- };
|