/* * QEMU Apple ParavirtualizedGraphics.framework device, PCI variant * * Copyright © 2023-2024 Phil Dennis-Jordan * * 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 a PCI device * aimed primarily at x86-64 macOS VMs. */ #include "qemu/osdep.h" #include "hw/pci/pci_device.h" #include "hw/pci/msi.h" #include "apple-gfx.h" #include "trace.h" #import OBJECT_DECLARE_SIMPLE_TYPE(AppleGFXPCIState, APPLE_GFX_PCI) struct AppleGFXPCIState { PCIDevice parent_obj; AppleGFXState common; }; static const char *apple_gfx_pci_option_rom_path = NULL; static void apple_gfx_init_option_rom_path(void) { NSURL *option_rom_url = PGCopyOptionROMURL(); const char *option_rom_path = option_rom_url.fileSystemRepresentation; apple_gfx_pci_option_rom_path = g_strdup(option_rom_path); [option_rom_url release]; } static void apple_gfx_pci_init(Object *obj) { AppleGFXPCIState *s = APPLE_GFX_PCI(obj); if (!apple_gfx_pci_option_rom_path) { /* * The following is done on device not class init to avoid running * ObjC code before fork() in -daemonize mode. */ PCIDeviceClass *pci = PCI_DEVICE_CLASS(object_get_class(obj)); apple_gfx_init_option_rom_path(); pci->romfile = apple_gfx_pci_option_rom_path; } apple_gfx_common_init(obj, &s->common, TYPE_APPLE_GFX_PCI); } typedef struct AppleGFXPCIInterruptJob { PCIDevice *device; uint32_t vector; } AppleGFXPCIInterruptJob; static void apple_gfx_pci_raise_interrupt(void *opaque) { AppleGFXPCIInterruptJob *job = opaque; if (msi_enabled(job->device)) { msi_notify(job->device, job->vector); } g_free(job); } static void apple_gfx_pci_interrupt(PCIDevice *dev, uint32_t vector) { AppleGFXPCIInterruptJob *job; trace_apple_gfx_raise_irq(vector); job = g_malloc0(sizeof(*job)); job->device = dev; job->vector = vector; aio_bh_schedule_oneshot(qemu_get_aio_context(), apple_gfx_pci_raise_interrupt, job); } static void apple_gfx_pci_realize(PCIDevice *dev, Error **errp) { AppleGFXPCIState *s = APPLE_GFX_PCI(dev); int ret; pci_register_bar(dev, PG_PCI_BAR_MMIO, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->common.iomem_gfx); ret = msi_init(dev, 0x0 /* config offset; 0 = find space */, PG_PCI_MAX_MSI_VECTORS, true /* msi64bit */, false /* msi_per_vector_mask */, errp); if (ret != 0) { return; } @autoreleasepool { PGDeviceDescriptor *desc = [PGDeviceDescriptor new]; desc.raiseInterrupt = ^(uint32_t vector) { apple_gfx_pci_interrupt(dev, vector); }; apple_gfx_common_realize(&s->common, DEVICE(dev), desc, errp); [desc release]; desc = nil; } } static void apple_gfx_pci_reset(Object *obj, ResetType type) { AppleGFXPCIState *s = APPLE_GFX_PCI(obj); [s->common.pgdev reset]; } static const Property apple_gfx_pci_properties[] = { DEFINE_PROP_ARRAY("display-modes", AppleGFXPCIState, common.num_display_modes, common.display_modes, qdev_prop_apple_gfx_display_mode, AppleGFXDisplayMode), }; static void apple_gfx_pci_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *pci = PCI_DEVICE_CLASS(klass); ResettableClass *rc = RESETTABLE_CLASS(klass); rc->phases.hold = apple_gfx_pci_reset; dc->desc = "macOS Paravirtualized Graphics PCI Display Controller"; dc->hotpluggable = false; set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); pci->vendor_id = PG_PCI_VENDOR_ID; pci->device_id = PG_PCI_DEVICE_ID; pci->class_id = PCI_CLASS_DISPLAY_OTHER; pci->realize = apple_gfx_pci_realize; device_class_set_props(dc, apple_gfx_pci_properties); } static const TypeInfo apple_gfx_pci_types[] = { { .name = TYPE_APPLE_GFX_PCI, .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(AppleGFXPCIState), .class_init = apple_gfx_pci_class_init, .instance_init = apple_gfx_pci_init, .interfaces = (InterfaceInfo[]) { { INTERFACE_PCIE_DEVICE }, { }, }, } }; DEFINE_TYPES(apple_gfx_pci_types)