xref: /qemu/hw/display/apple-gfx-pci.m (revision 513823e7521a09ed7ad1e32e6454bac3b2cbf52d)
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
23OBJECT_DECLARE_SIMPLE_TYPE(AppleGFXPCIState, APPLE_GFX_PCI)
24
25struct AppleGFXPCIState {
26    PCIDevice parent_obj;
27
28    AppleGFXState common;
29};
30
31static const char *apple_gfx_pci_option_rom_path = NULL;
32
33static 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
41static 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
58typedef struct AppleGFXPCIInterruptJob {
59    PCIDevice *device;
60    uint32_t vector;
61} AppleGFXPCIInterruptJob;
62
63static 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
73static 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
85static 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
112static void apple_gfx_pci_reset(Object *obj, ResetType type)
113{
114    AppleGFXPCIState *s = APPLE_GFX_PCI(obj);
115    [s->common.pgdev reset];
116}
117
118static 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
124static void apple_gfx_pci_class_init(ObjectClass *klass, 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
143static 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 = (InterfaceInfo[]) {
151            { INTERFACE_PCIE_DEVICE },
152            { },
153        },
154    }
155};
156DEFINE_TYPES(apple_gfx_pci_types)
157
158