| 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 const Property apple_gfx_pci_properties[] = { |
| 119 | DEFINE_PROP_ARRAY("display-modes", AppleGFXPCIState, |
| 120 | common.num_display_modes, common.display_modes, |
| 121 | qdev_prop_apple_gfx_display_mode, AppleGFXDisplayMode), |
| 122 | }; |
| 123 | |
| 124 | static void apple_gfx_pci_class_init(ObjectClass *klass, const void *data) |
| 125 | { |
| 126 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 127 | PCIDeviceClass *pci = PCI_DEVICE_CLASS(klass); |
| 128 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 129 | |
| 130 | rc->phases.hold = apple_gfx_pci_reset; |
| 131 | dc->desc = "macOS Paravirtualized Graphics PCI Display Controller"; |
| 132 | dc->hotpluggable = false; |
| 133 | set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); |
| 134 | |
| 135 | pci->vendor_id = PG_PCI_VENDOR_ID; |
| 136 | pci->device_id = PG_PCI_DEVICE_ID; |
| 137 | pci->class_id = PCI_CLASS_DISPLAY_OTHER; |
| 138 | pci->realize = apple_gfx_pci_realize; |
| 139 | |
| 140 | device_class_set_props(dc, apple_gfx_pci_properties); |
| 141 | } |
| 142 | |
| 143 | static const TypeInfo apple_gfx_pci_types[] = { |
| 144 | { |
| 145 | .name = TYPE_APPLE_GFX_PCI, |
| 146 | .parent = TYPE_PCI_DEVICE, |
| 147 | .instance_size = sizeof(AppleGFXPCIState), |
| 148 | .class_init = apple_gfx_pci_class_init, |
| 149 | .instance_init = apple_gfx_pci_init, |
| 150 | .interfaces = (const InterfaceInfo[]) { |
| 151 | { INTERFACE_PCIE_DEVICE }, |
| 152 | { }, |
| 153 | }, |
| 154 | } |
| 155 | }; |
| 156 | DEFINE_TYPES(apple_gfx_pci_types) |
| 157 |