mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
hw/display/apple-gfx: Introduce ParavirtualizedGraphics.Framework support
MacOS provides a framework (library) that allows any vmm to implement a paravirtualized 3d graphics passthrough to the host metal stack called ParavirtualizedGraphics.Framework (PVG). The library abstracts away almost every aspect of the paravirtualized device model and only provides and receives callbacks on MMIO access as well as to share memory address space between the VM and PVG. This patch implements a QEMU device that drives PVG for the VMApple variant of it. Signed-off-by: Alexander Graf <graf@amazon.com> Co-authored-by: Alexander Graf <graf@amazon.com> Subsequent changes: * Cherry-pick/rebase conflict fixes, API use updates. * Moved from hw/vmapple/ (useful outside that machine type) * Overhaul of threading model, many thread safety improvements. * Asynchronous rendering. * Memory and object lifetime fixes. * Refactoring to split generic and (vmapple) MMIO variant specific code. Implementation wise, most of the complexity lies in the differing threading models of ParavirtualizedGraphics.framework, which uses libdispatch and internal locks, versus QEMU, which heavily uses the BQL, especially during memory-mapped device I/O. Great care has therefore been taken to prevent deadlocks by never calling into PVG methods while holding the BQL, and similarly never acquiring the BQL in a callback from PVG. Different strategies have been used (libdispatch, blocking and non-blocking BHs, RCU, etc.) depending on the specific requirements at each framework entry and exit point. Signed-off-by: Phil Dennis-Jordan <phil@philjordan.eu> Reviewed-by: Akihiko Odaki <akihiko.odaki@daynix.com> Tested-by: Akihiko Odaki <akihiko.odaki@daynix.com> Message-ID: <20241223221645.29911-3-phil@philjordan.eu> [PMD: Re-ordered imported headers, style fixups] Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
This commit is contained in:
committed by
Philippe Mathieu-Daudé
parent
f5ab12caba
commit
2352159c97
@@ -140,3 +140,12 @@ config XLNX_DISPLAYPORT
|
||||
|
||||
config DM163
|
||||
bool
|
||||
|
||||
config MAC_PVG
|
||||
bool
|
||||
default y
|
||||
|
||||
config MAC_PVG_MMIO
|
||||
bool
|
||||
depends on MAC_PVG && AARCH64
|
||||
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* QEMU Apple ParavirtualizedGraphics.framework device, MMIO (arm64) variant
|
||||
*
|
||||
* 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 as an MMIO-based
|
||||
* system device for macOS on arm64 VMs.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "block/aio-wait.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/irq.h"
|
||||
#include "apple-gfx.h"
|
||||
#include "trace.h"
|
||||
|
||||
#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h>
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(AppleGFXMMIOState, APPLE_GFX_MMIO)
|
||||
|
||||
/*
|
||||
* ParavirtualizedGraphics.Framework only ships header files for the PCI
|
||||
* variant which does not include IOSFC descriptors and host devices. We add
|
||||
* their definitions here so that we can also work with the ARM version.
|
||||
*/
|
||||
typedef bool(^IOSFCRaiseInterrupt)(uint32_t vector);
|
||||
typedef bool(^IOSFCUnmapMemory)(void *, void *, void *, void *, void *, void *);
|
||||
typedef bool(^IOSFCMapMemory)(uint64_t phys, uint64_t len, bool ro, void **va,
|
||||
void *, void *);
|
||||
|
||||
@interface PGDeviceDescriptor (IOSurfaceMapper)
|
||||
@property (readwrite, nonatomic) bool usingIOSurfaceMapper;
|
||||
@end
|
||||
|
||||
@interface PGIOSurfaceHostDeviceDescriptor : NSObject
|
||||
-(PGIOSurfaceHostDeviceDescriptor *)init;
|
||||
@property (readwrite, nonatomic, copy, nullable) IOSFCMapMemory mapMemory;
|
||||
@property (readwrite, nonatomic, copy, nullable) IOSFCUnmapMemory unmapMemory;
|
||||
@property (readwrite, nonatomic, copy, nullable) IOSFCRaiseInterrupt raiseInterrupt;
|
||||
@end
|
||||
|
||||
@interface PGIOSurfaceHostDevice : NSObject
|
||||
-(instancetype)initWithDescriptor:(PGIOSurfaceHostDeviceDescriptor *)desc;
|
||||
-(uint32_t)mmioReadAtOffset:(size_t)offset;
|
||||
-(void)mmioWriteAtOffset:(size_t)offset value:(uint32_t)value;
|
||||
@end
|
||||
|
||||
struct AppleGFXMapSurfaceMemoryJob;
|
||||
struct AppleGFXMMIOState {
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
AppleGFXState common;
|
||||
|
||||
qemu_irq irq_gfx;
|
||||
qemu_irq irq_iosfc;
|
||||
MemoryRegion iomem_iosfc;
|
||||
PGIOSurfaceHostDevice *pgiosfc;
|
||||
};
|
||||
|
||||
typedef struct AppleGFXMMIOJob {
|
||||
AppleGFXMMIOState *state;
|
||||
uint64_t offset;
|
||||
uint64_t value;
|
||||
bool completed;
|
||||
} AppleGFXMMIOJob;
|
||||
|
||||
static void iosfc_do_read(void *opaque)
|
||||
{
|
||||
AppleGFXMMIOJob *job = opaque;
|
||||
job->value = [job->state->pgiosfc mmioReadAtOffset:job->offset];
|
||||
qatomic_set(&job->completed, true);
|
||||
aio_wait_kick();
|
||||
}
|
||||
|
||||
static uint64_t iosfc_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
AppleGFXMMIOJob job = {
|
||||
.state = opaque,
|
||||
.offset = offset,
|
||||
.completed = false,
|
||||
};
|
||||
dispatch_queue_t queue =
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
|
||||
dispatch_async_f(queue, &job, iosfc_do_read);
|
||||
AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
|
||||
|
||||
trace_apple_gfx_mmio_iosfc_read(offset, job.value);
|
||||
return job.value;
|
||||
}
|
||||
|
||||
static void iosfc_do_write(void *opaque)
|
||||
{
|
||||
AppleGFXMMIOJob *job = opaque;
|
||||
[job->state->pgiosfc mmioWriteAtOffset:job->offset value:job->value];
|
||||
qatomic_set(&job->completed, true);
|
||||
aio_wait_kick();
|
||||
}
|
||||
|
||||
static void iosfc_write(void *opaque, hwaddr offset, uint64_t val,
|
||||
unsigned size)
|
||||
{
|
||||
AppleGFXMMIOJob job = {
|
||||
.state = opaque,
|
||||
.offset = offset,
|
||||
.value = val,
|
||||
.completed = false,
|
||||
};
|
||||
dispatch_queue_t queue =
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
|
||||
dispatch_async_f(queue, &job, iosfc_do_write);
|
||||
AIO_WAIT_WHILE(NULL, !qatomic_read(&job.completed));
|
||||
|
||||
trace_apple_gfx_mmio_iosfc_write(offset, val);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps apple_iosfc_ops = {
|
||||
.read = iosfc_read,
|
||||
.write = iosfc_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 8,
|
||||
},
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 8,
|
||||
},
|
||||
};
|
||||
|
||||
static void raise_irq_bh(void *opaque)
|
||||
{
|
||||
qemu_irq *irq = opaque;
|
||||
|
||||
qemu_irq_pulse(*irq);
|
||||
}
|
||||
|
||||
static void *apple_gfx_mmio_map_surface_memory(uint64_t guest_physical_address,
|
||||
uint64_t length, bool read_only)
|
||||
{
|
||||
void *mem;
|
||||
MemoryRegion *region = NULL;
|
||||
|
||||
RCU_READ_LOCK_GUARD();
|
||||
mem = apple_gfx_host_ptr_for_gpa_range(guest_physical_address,
|
||||
length, read_only, ®ion);
|
||||
if (mem) {
|
||||
memory_region_ref(region);
|
||||
}
|
||||
return mem;
|
||||
}
|
||||
|
||||
static bool apple_gfx_mmio_unmap_surface_memory(void *ptr)
|
||||
{
|
||||
MemoryRegion *region;
|
||||
ram_addr_t offset = 0;
|
||||
|
||||
RCU_READ_LOCK_GUARD();
|
||||
region = memory_region_from_host(ptr, &offset);
|
||||
if (!region) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: memory at %p to be unmapped not found.\n",
|
||||
__func__, ptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
trace_apple_gfx_iosfc_unmap_memory_region(ptr, region);
|
||||
memory_region_unref(region);
|
||||
return true;
|
||||
}
|
||||
|
||||
static PGIOSurfaceHostDevice *apple_gfx_prepare_iosurface_host_device(
|
||||
AppleGFXMMIOState *s)
|
||||
{
|
||||
PGIOSurfaceHostDeviceDescriptor *iosfc_desc =
|
||||
[PGIOSurfaceHostDeviceDescriptor new];
|
||||
PGIOSurfaceHostDevice *iosfc_host_dev;
|
||||
|
||||
iosfc_desc.mapMemory =
|
||||
^bool(uint64_t phys, uint64_t len, bool ro, void **va, void *e, void *f) {
|
||||
*va = apple_gfx_mmio_map_surface_memory(phys, len, ro);
|
||||
|
||||
trace_apple_gfx_iosfc_map_memory(phys, len, ro, va, e, f, *va);
|
||||
|
||||
return *va != NULL;
|
||||
};
|
||||
|
||||
iosfc_desc.unmapMemory =
|
||||
^bool(void *va, void *b, void *c, void *d, void *e, void *f) {
|
||||
return apple_gfx_mmio_unmap_surface_memory(va);
|
||||
};
|
||||
|
||||
iosfc_desc.raiseInterrupt = ^bool(uint32_t vector) {
|
||||
trace_apple_gfx_iosfc_raise_irq(vector);
|
||||
aio_bh_schedule_oneshot(qemu_get_aio_context(),
|
||||
raise_irq_bh, &s->irq_iosfc);
|
||||
return true;
|
||||
};
|
||||
|
||||
iosfc_host_dev =
|
||||
[[PGIOSurfaceHostDevice alloc] initWithDescriptor:iosfc_desc];
|
||||
[iosfc_desc release];
|
||||
return iosfc_host_dev;
|
||||
}
|
||||
|
||||
static void apple_gfx_mmio_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
@autoreleasepool {
|
||||
AppleGFXMMIOState *s = APPLE_GFX_MMIO(dev);
|
||||
PGDeviceDescriptor *desc = [PGDeviceDescriptor new];
|
||||
|
||||
desc.raiseInterrupt = ^(uint32_t vector) {
|
||||
trace_apple_gfx_raise_irq(vector);
|
||||
aio_bh_schedule_oneshot(qemu_get_aio_context(),
|
||||
raise_irq_bh, &s->irq_gfx);
|
||||
};
|
||||
|
||||
desc.usingIOSurfaceMapper = true;
|
||||
s->pgiosfc = apple_gfx_prepare_iosurface_host_device(s);
|
||||
|
||||
if (!apple_gfx_common_realize(&s->common, dev, desc, errp)) {
|
||||
[s->pgiosfc release];
|
||||
s->pgiosfc = nil;
|
||||
}
|
||||
|
||||
[desc release];
|
||||
desc = nil;
|
||||
}
|
||||
}
|
||||
|
||||
static void apple_gfx_mmio_init(Object *obj)
|
||||
{
|
||||
AppleGFXMMIOState *s = APPLE_GFX_MMIO(obj);
|
||||
|
||||
apple_gfx_common_init(obj, &s->common, TYPE_APPLE_GFX_MMIO);
|
||||
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->common.iomem_gfx);
|
||||
memory_region_init_io(&s->iomem_iosfc, obj, &apple_iosfc_ops, s,
|
||||
TYPE_APPLE_GFX_MMIO, 0x10000);
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem_iosfc);
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq_gfx);
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq_iosfc);
|
||||
}
|
||||
|
||||
static void apple_gfx_mmio_reset(Object *obj, ResetType type)
|
||||
{
|
||||
AppleGFXMMIOState *s = APPLE_GFX_MMIO(obj);
|
||||
[s->common.pgdev reset];
|
||||
}
|
||||
|
||||
|
||||
static void apple_gfx_mmio_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
|
||||
rc->phases.hold = apple_gfx_mmio_reset;
|
||||
dc->hotpluggable = false;
|
||||
dc->realize = apple_gfx_mmio_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo apple_gfx_mmio_types[] = {
|
||||
{
|
||||
.name = TYPE_APPLE_GFX_MMIO,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(AppleGFXMMIOState),
|
||||
.class_init = apple_gfx_mmio_class_init,
|
||||
.instance_init = apple_gfx_mmio_init,
|
||||
}
|
||||
};
|
||||
DEFINE_TYPES(apple_gfx_mmio_types)
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Data structures and functions shared between variants of the macOS
|
||||
* ParavirtualizedGraphics.framework based apple-gfx display adapter.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef QEMU_APPLE_GFX_H
|
||||
#define QEMU_APPLE_GFX_H
|
||||
|
||||
#include "qemu/queue.h"
|
||||
#include "exec/memory.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "ui/surface.h"
|
||||
|
||||
#define TYPE_APPLE_GFX_MMIO "apple-gfx-mmio"
|
||||
|
||||
@class PGDeviceDescriptor;
|
||||
@protocol PGDevice;
|
||||
@protocol PGDisplay;
|
||||
@protocol MTLDevice;
|
||||
@protocol MTLTexture;
|
||||
@protocol MTLCommandQueue;
|
||||
|
||||
typedef QTAILQ_HEAD(, PGTask_s) PGTaskList;
|
||||
|
||||
typedef struct AppleGFXState {
|
||||
/* Initialised on init/realize() */
|
||||
MemoryRegion iomem_gfx;
|
||||
id<PGDevice> pgdev;
|
||||
id<PGDisplay> pgdisp;
|
||||
QemuConsole *con;
|
||||
id<MTLDevice> mtl;
|
||||
id<MTLCommandQueue> mtl_queue;
|
||||
|
||||
/* List `tasks` is protected by task_mutex */
|
||||
QemuMutex task_mutex;
|
||||
PGTaskList tasks;
|
||||
|
||||
/* Mutable state (BQL protected) */
|
||||
QEMUCursor *cursor;
|
||||
DisplaySurface *surface;
|
||||
id<MTLTexture> texture;
|
||||
int8_t pending_frames; /* # guest frames in the rendering pipeline */
|
||||
bool gfx_update_requested; /* QEMU display system wants a new frame */
|
||||
bool new_frame_ready; /* Guest has rendered a frame, ready to be used */
|
||||
bool using_managed_texture_storage;
|
||||
uint32_t rendering_frame_width;
|
||||
uint32_t rendering_frame_height;
|
||||
|
||||
/* Mutable state (atomic) */
|
||||
bool cursor_show;
|
||||
} AppleGFXState;
|
||||
|
||||
void apple_gfx_common_init(Object *obj, AppleGFXState *s, const char* obj_name);
|
||||
bool apple_gfx_common_realize(AppleGFXState *s, DeviceState *dev,
|
||||
PGDeviceDescriptor *desc, Error **errp);
|
||||
void *apple_gfx_host_ptr_for_gpa_range(uint64_t guest_physical,
|
||||
uint64_t length, bool read_only,
|
||||
MemoryRegion **mapping_in_region);
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,12 @@ system_ss.add(when: 'CONFIG_ARTIST', if_true: files('artist.c'))
|
||||
|
||||
system_ss.add(when: 'CONFIG_ATI_VGA', if_true: [files('ati.c', 'ati_2d.c', 'ati_dbg.c'), pixman])
|
||||
|
||||
if host_os == 'darwin'
|
||||
system_ss.add(when: 'CONFIG_MAC_PVG', if_true: [files('apple-gfx.m'), pvg, metal])
|
||||
if cpu == 'aarch64'
|
||||
system_ss.add(when: 'CONFIG_MAC_PVG_MMIO', if_true: [files('apple-gfx-mmio.m'), pvg, metal])
|
||||
endif
|
||||
endif
|
||||
|
||||
if config_all_devices.has_key('CONFIG_VIRTIO_GPU')
|
||||
virtio_gpu_ss = ss.source_set()
|
||||
|
||||
@@ -194,3 +194,31 @@ dm163_bits_ppi(unsigned dest_width) "dest_width : %u"
|
||||
dm163_leds(int led, uint32_t value) "led %d: 0x%x"
|
||||
dm163_channels(int channel, uint8_t value) "channel %d: 0x%x"
|
||||
dm163_refresh_rate(uint32_t rr) "refresh rate %d"
|
||||
|
||||
# apple-gfx.m
|
||||
apple_gfx_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64
|
||||
apple_gfx_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64
|
||||
apple_gfx_create_task(uint32_t vm_size, void *va) "vm_size=0x%x base_addr=%p"
|
||||
apple_gfx_destroy_task(void *task, unsigned int num_mapped_regions) "task=%p, task->mapped_regions->len=%u"
|
||||
apple_gfx_map_memory(void *task, uint32_t range_count, uint64_t virtual_offset, uint32_t read_only) "task=%p range_count=0x%x virtual_offset=0x%"PRIx64" read_only=%d"
|
||||
apple_gfx_map_memory_range(uint32_t i, uint64_t phys_addr, uint64_t phys_len) "[%d] phys_addr=0x%"PRIx64" phys_len=0x%"PRIx64
|
||||
apple_gfx_remap(uint64_t retval, void *source_ptr, uint64_t target) "retval=%"PRId64" source=%p target=0x%"PRIx64
|
||||
apple_gfx_unmap_memory(void *task, uint64_t virtual_offset, uint64_t length) "task=%p virtual_offset=0x%"PRIx64" length=0x%"PRIx64
|
||||
apple_gfx_read_memory(uint64_t phys_address, uint64_t length, void *dst) "phys_addr=0x%"PRIx64" length=0x%"PRIx64" dest=%p"
|
||||
apple_gfx_raise_irq(uint32_t vector) "vector=0x%x"
|
||||
apple_gfx_new_frame(void) ""
|
||||
apple_gfx_mode_change(uint64_t x, uint64_t y) "x=%"PRId64" y=%"PRId64
|
||||
apple_gfx_cursor_set(uint32_t bpp, uint64_t width, uint64_t height) "bpp=%d width=%"PRId64" height=0x%"PRId64
|
||||
apple_gfx_cursor_show(uint32_t show) "show=%d"
|
||||
apple_gfx_cursor_move(void) ""
|
||||
apple_gfx_common_init(const char *device_name, size_t mmio_size) "device: %s; MMIO size: %zu bytes"
|
||||
|
||||
# apple-gfx-mmio.m
|
||||
apple_gfx_mmio_iosfc_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64
|
||||
apple_gfx_mmio_iosfc_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64
|
||||
apple_gfx_iosfc_map_memory(uint64_t phys, uint64_t len, uint32_t ro, void *va, void *e, void *f, void* va_result) "phys=0x%"PRIx64" len=0x%"PRIx64" ro=%d va=%p e=%p f=%p -> *va=%p"
|
||||
apple_gfx_iosfc_map_memory_new_region(size_t i, void *region, uint64_t start, uint64_t end) "index=%zu, region=%p, 0x%"PRIx64"-0x%"PRIx64
|
||||
apple_gfx_iosfc_unmap_memory(void *a, void *b, void *c, void *d, void *e, void *f) "a=%p b=%p c=%p d=%p e=%p f=%p"
|
||||
apple_gfx_iosfc_unmap_memory_region(void* mem, void *region) "unmapping @ %p from memory region %p"
|
||||
apple_gfx_iosfc_raise_irq(uint32_t vector) "vector=0x%x"
|
||||
|
||||
|
||||
@@ -817,6 +817,8 @@ socket = []
|
||||
version_res = []
|
||||
coref = []
|
||||
iokit = []
|
||||
pvg = not_found
|
||||
metal = []
|
||||
emulator_link_args = []
|
||||
midl = not_found
|
||||
widl = not_found
|
||||
@@ -838,6 +840,8 @@ elif host_os == 'darwin'
|
||||
coref = dependency('appleframeworks', modules: 'CoreFoundation')
|
||||
iokit = dependency('appleframeworks', modules: 'IOKit', required: false)
|
||||
host_dsosuf = '.dylib'
|
||||
pvg = dependency('appleframeworks', modules: 'ParavirtualizedGraphics')
|
||||
metal = dependency('appleframeworks', modules: 'Metal')
|
||||
elif host_os == 'sunos'
|
||||
socket = [cc.find_library('socket'),
|
||||
cc.find_library('nsl'),
|
||||
|
||||
Reference in New Issue
Block a user