|
| 1 | +/* |
| 2 | + * QEMU Apple ParavirtualizedGraphics.framework device, PCI variant |
| 3 | + * |
| 4 | + * Copyright © 2023-2024 Phil Dennis-Jordan |
| 5 | + * |
| 6 | + * SPDX-License-Identifier: GPL-2.0-or-later |
| 7 | + * |
| 8 | + * ParavirtualizedGraphics.framework is a set of libraries that macOS provides |
| 9 | + * which implements 3d graphics passthrough to the host as well as a |
| 10 | + * proprietary guest communication channel to drive it. This device model |
| 11 | + * implements support to drive that library from within QEMU as a PCI device |
| 12 | + * aimed primarily at x86-64 macOS VMs. |
| 13 | + */ |
| 14 | + |
| 15 | +#include "qemu/osdep.h" |
| 16 | +#include "hw/pci/pci_device.h" |
| 17 | +#include "hw/pci/msi.h" |
| 18 | +#include "apple-gfx.h" |
| 19 | +#include "trace.h" |
| 20 | + |
| 21 | +#import <ParavirtualizedGraphics/ParavirtualizedGraphics.h> |
| 22 | + |
| 23 | +OBJECT_DECLARE_SIMPLE_TYPE(AppleGFXPCIState, APPLE_GFX_PCI) |
| 24 | + |
| 25 | +struct AppleGFXPCIState { |
| 26 | + PCIDevice parent_obj; |
| 27 | + |
| 28 | + AppleGFXState common; |
| 29 | +}; |
| 30 | + |
| 31 | +static const char *apple_gfx_pci_option_rom_path = NULL; |
| 32 | + |
| 33 | +static void apple_gfx_init_option_rom_path(void) |
| 34 | +{ |
| 35 | + NSURL *option_rom_url = PGCopyOptionROMURL(); |
| 36 | + const char *option_rom_path = option_rom_url.fileSystemRepresentation; |
| 37 | + apple_gfx_pci_option_rom_path = g_strdup(option_rom_path); |
| 38 | + [option_rom_url release]; |
| 39 | +} |
| 40 | + |
| 41 | +static void apple_gfx_pci_init(Object *obj) |
| 42 | +{ |
| 43 | + AppleGFXPCIState *s = APPLE_GFX_PCI(obj); |
| 44 | + |
| 45 | + if (!apple_gfx_pci_option_rom_path) { |
| 46 | + /* |
| 47 | + * The following is done on device not class init to avoid running |
| 48 | + * ObjC code before fork() in -daemonize mode. |
| 49 | + */ |
| 50 | + PCIDeviceClass *pci = PCI_DEVICE_CLASS(object_get_class(obj)); |
| 51 | + apple_gfx_init_option_rom_path(); |
| 52 | + pci->romfile = apple_gfx_pci_option_rom_path; |
| 53 | + } |
| 54 | + |
| 55 | + apple_gfx_common_init(obj, &s->common, TYPE_APPLE_GFX_PCI); |
| 56 | +} |
| 57 | + |
| 58 | +typedef struct AppleGFXPCIInterruptJob { |
| 59 | + PCIDevice *device; |
| 60 | + uint32_t vector; |
| 61 | +} AppleGFXPCIInterruptJob; |
| 62 | + |
| 63 | +static void apple_gfx_pci_raise_interrupt(void *opaque) |
| 64 | +{ |
| 65 | + AppleGFXPCIInterruptJob *job = opaque; |
| 66 | + |
| 67 | + if (msi_enabled(job->device)) { |
| 68 | + msi_notify(job->device, job->vector); |
| 69 | + } |
| 70 | + g_free(job); |
| 71 | +} |
| 72 | + |
| 73 | +static void apple_gfx_pci_interrupt(PCIDevice *dev, uint32_t vector) |
| 74 | +{ |
| 75 | + AppleGFXPCIInterruptJob *job; |
| 76 | + |
| 77 | + trace_apple_gfx_raise_irq(vector); |
| 78 | + job = g_malloc0(sizeof(*job)); |
| 79 | + job->device = dev; |
| 80 | + job->vector = vector; |
| 81 | + aio_bh_schedule_oneshot(qemu_get_aio_context(), |
| 82 | + apple_gfx_pci_raise_interrupt, job); |
| 83 | +} |
| 84 | + |
| 85 | +static void apple_gfx_pci_realize(PCIDevice *dev, Error **errp) |
| 86 | +{ |
| 87 | + AppleGFXPCIState *s = APPLE_GFX_PCI(dev); |
| 88 | + int ret; |
| 89 | + |
| 90 | + pci_register_bar(dev, PG_PCI_BAR_MMIO, |
| 91 | + PCI_BASE_ADDRESS_SPACE_MEMORY, &s->common.iomem_gfx); |
| 92 | + |
| 93 | + ret = msi_init(dev, 0x0 /* config offset; 0 = find space */, |
| 94 | + PG_PCI_MAX_MSI_VECTORS, true /* msi64bit */, |
| 95 | + false /* msi_per_vector_mask */, errp); |
| 96 | + if (ret != 0) { |
| 97 | + return; |
| 98 | + } |
| 99 | + |
| 100 | + @autoreleasepool { |
| 101 | + PGDeviceDescriptor *desc = [PGDeviceDescriptor new]; |
| 102 | + desc.raiseInterrupt = ^(uint32_t vector) { |
| 103 | + apple_gfx_pci_interrupt(dev, vector); |
| 104 | + }; |
| 105 | + |
| 106 | + apple_gfx_common_realize(&s->common, DEVICE(dev), desc, errp); |
| 107 | + [desc release]; |
| 108 | + desc = nil; |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +static void apple_gfx_pci_reset(Object *obj, ResetType type) |
| 113 | +{ |
| 114 | + AppleGFXPCIState *s = APPLE_GFX_PCI(obj); |
| 115 | + [s->common.pgdev reset]; |
| 116 | +} |
| 117 | + |
| 118 | +static void apple_gfx_pci_class_init(ObjectClass *klass, void *data) |
| 119 | +{ |
| 120 | + DeviceClass *dc = DEVICE_CLASS(klass); |
| 121 | + PCIDeviceClass *pci = PCI_DEVICE_CLASS(klass); |
| 122 | + ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 123 | + |
| 124 | + rc->phases.hold = apple_gfx_pci_reset; |
| 125 | + dc->desc = "macOS Paravirtualized Graphics PCI Display Controller"; |
| 126 | + dc->hotpluggable = false; |
| 127 | + set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); |
| 128 | + |
| 129 | + pci->vendor_id = PG_PCI_VENDOR_ID; |
| 130 | + pci->device_id = PG_PCI_DEVICE_ID; |
| 131 | + pci->class_id = PCI_CLASS_DISPLAY_OTHER; |
| 132 | + pci->realize = apple_gfx_pci_realize; |
| 133 | + |
| 134 | + /* TODO: Property for setting mode list */ |
| 135 | +} |
| 136 | + |
| 137 | +static const TypeInfo apple_gfx_pci_types[] = { |
| 138 | + { |
| 139 | + .name = TYPE_APPLE_GFX_PCI, |
| 140 | + .parent = TYPE_PCI_DEVICE, |
| 141 | + .instance_size = sizeof(AppleGFXPCIState), |
| 142 | + .class_init = apple_gfx_pci_class_init, |
| 143 | + .instance_init = apple_gfx_pci_init, |
| 144 | + .interfaces = (InterfaceInfo[]) { |
| 145 | + { INTERFACE_PCIE_DEVICE }, |
| 146 | + { }, |
| 147 | + }, |
| 148 | + } |
| 149 | +}; |
| 150 | +DEFINE_TYPES(apple_gfx_pci_types) |
| 151 | + |
0 commit comments