Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2219884/?format=api
{ "id": 2219884, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2219884/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260405072857.66484-9-scottjgo@gmail.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/1.2/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260405072857.66484-9-scottjgo@gmail.com>", "list_archive_url": null, "date": "2026-04-05T07:28:52", "name": "[RFC,08/10] vfio/apple: Add IOMMU container and PCI device", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "aa5b3e273b83c4ab250566f08f5010abc0f045da", "submitter": { "id": 93060, "url": "http://patchwork.ozlabs.org/api/1.2/people/93060/?format=api", "name": "Scott J. Goldman", "email": "scottjgo@gmail.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260405072857.66484-9-scottjgo@gmail.com/mbox/", "series": [ { "id": 498765, "url": "http://patchwork.ozlabs.org/api/1.2/series/498765/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=498765", "date": "2026-04-05T07:28:44", "name": "vfio: PCI device passthrough on Apple Silicon Macs", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/498765/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2219884/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2219884/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=OotdOmNB;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)" ], "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fpPJT4FFKz1y2d\n\tfor <incoming@patchwork.ozlabs.org>; Sun, 05 Apr 2026 17:30:49 +1000 (AEST)", "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1w9HvV-0003zV-4i; Sun, 05 Apr 2026 03:29:45 -0400", "from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <scottjgo@gmail.com>)\n id 1w9HvT-0003yT-Sp\n for qemu-devel@nongnu.org; Sun, 05 Apr 2026 03:29:43 -0400", "from mail-dy1-x132f.google.com ([2607:f8b0:4864:20::132f])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <scottjgo@gmail.com>)\n id 1w9HvP-0007PI-C8\n for qemu-devel@nongnu.org; Sun, 05 Apr 2026 03:29:43 -0400", "by mail-dy1-x132f.google.com with SMTP id\n 5a478bee46e88-2c7d8bbad06so7605378eec.1\n for <qemu-devel@nongnu.org>; Sun, 05 Apr 2026 00:29:39 -0700 (PDT)", "from localhost.localdomain ([2601:645:8200:47:41e4:ff2b:ff70:4d75])\n by smtp.gmail.com with ESMTPSA id\n 5a478bee46e88-2cb92ea0ef1sm7636502eec.21.2026.04.05.00.29.35\n (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256);\n Sun, 05 Apr 2026 00:29:36 -0700 (PDT)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1775374178; x=1775978978; darn=nongnu.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=6JbIKg+vYqclDU8Q8UgiO3lCc9fAOAsgjLkxdJlPCOA=;\n b=OotdOmNBOSKBM5hFKLRKYLlvPEtVEKxaQZV6RfxxDaIOReouRM3v5Wzlsd8vEF4/FJ\n GhnN4tvjA0ckj9YYNQ4MLqrQRwguTaELgJZDXiEKKV1rjGDVTs7YddLglgMlHsZIZAUC\n BH4SEcownSN3vO8rwwkuT9MwcihNlAkO3pP8ofOKoLNT+UcFOA9z06eTdVM+GAnuxFdp\n DFiLniNn55ehp8fz9YULqAnDUeghPdoAmkPg756rriyOpfm9wtd9ZVph0CLWGmT05JS3\n ijjPEBQRJZUaWi9lKslwhUEuT2dffpfivihCm4l32pkjDonMWHn5N1628aNRcsw0iLSv\n iZ7w==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1775374178; x=1775978978;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=6JbIKg+vYqclDU8Q8UgiO3lCc9fAOAsgjLkxdJlPCOA=;\n b=OYpCdFJahLcrhNcQRGjVQGOUXABpWg36NWTq1Md+1iM0ZPeb+18teFJZv2ZVC9weMH\n PlLoQa8LwZXCeb8+xA6YtE3u5xztFh5inMmCFeOGUlVSG/O56VW7dWwkoZwGUHhoyJGU\n eDLfgmhHfjMv8gtdENxhqXHqjFGD1+39bhG53AVi2eaYx9lIxrlYMr8mOg/3KqjJt2Ic\n DRpbBOyuC7DOB2G3+ZdNh4Yfhfg74CQj/aLM4s025wB3GvPtdDiS/NEuTagXSBwZKqX6\n wrLS0nUsdS3YqZDbQazDvk8vuRYxP6PTuf13+EpAy2rIOxs0LR8y9wFp+GnzYV3k+lu4\n CYow==", "X-Gm-Message-State": "AOJu0YxA2WyjhZaObQ0DKWHRkZC+104i6gMzwMEd5jtNqRDhUh6XMGyQ\n Hw9sVdHChrZSfFjpmTqUVWQQrK9yMQaA3o6LyJMXad/ZOVZNWsQECO2W7ee0PT1f39E=", "X-Gm-Gg": "AeBDies4/ZAMW577F1khBPd8ABqKisDxmGnmDbmPgqWs2tuzItAZc3uTcyXXQb49Aof\n 2LaY1pMs1rkvIus1rkvBgZ/BecF/TAlu0xGPyZaOJCYqjBXYmVMTXCF4jn7fkWlrcaXjyuqRfCW\n xTuSpTKXQRXF5IkINCi9EciHBHYZIZE6GsHXpa5UMY9mTSlFUTioAyDSOL4Ob1bk6da4blSVCbY\n M6He2sP/DkAFi6/FwsI78Mv2F030fXQMmpMAyolyuTjS9rxY/7ij/ur9+9a5NcMHXwHK16wLCvQ\n FR7Cz8s79gus99lRjFCz9LoFMS9Iqyr5sOfsFz9BYXleyWV5LFvFbQxzL1MNQL2+798SfA+fzKK\n YNGJN77+AnALixRZU5o1L2lsCSVjMiZV9CUE0b6uHsWy3R1xexclU0cbjlYbhFaVkgI5Ay9c5uC\n fwrCUdoNvHawe2VgMEvR7E90Ru9IneNl1CVJg9TLtGfY01IA2r/0SeTkDW6cnqCRlfH9hKHnaDN\n 9rvHfElIy35ID+Tgd8rk0u5xpo=", "X-Received": "by 2002:a05:7300:a506:b0:2c3:9981:e8ee with SMTP id\n 5a478bee46e88-2cbfa2d188cmr4200374eec.12.1775374177349;\n Sun, 05 Apr 2026 00:29:37 -0700 (PDT)", "From": "\"Scott J. Goldman\" <scottjgo@gmail.com>", "To": "qemu-devel@nongnu.org", "Cc": "alex@shazbot.org, clg@redhat.com, pbonzini@redhat.com, rbolshakov@ddn.com,\n phil@philjordan.eu, mst@redhat.com, john.levon@nutanix.com,\n thanos.makatos@nutanix.com, qemu-s390x@nongnu.org,\n \"Scott J. Goldman\" <scottjg@umich.edu>,\n \"Scott J. Goldman\" <scottjgo@gmail.com>", "Subject": "[RFC PATCH 08/10] vfio/apple: Add IOMMU container and PCI device", "Date": "Sun, 5 Apr 2026 00:28:52 -0700", "Message-ID": "<20260405072857.66484-9-scottjgo@gmail.com>", "X-Mailer": "git-send-email 2.50.1", "In-Reply-To": "<20260405072857.66484-1-scottjgo@gmail.com>", "References": "<20260405072857.66484-1-scottjgo@gmail.com>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "Received-SPF": "pass client-ip=2607:f8b0:4864:20::132f;\n envelope-from=scottjgo@gmail.com; helo=mail-dy1-x132f.google.com", "X-Spam_score_int": "-20", "X-Spam_score": "-2.1", "X-Spam_bar": "--", "X-Spam_report": "(-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001,\n RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no", "X-Spam_action": "no action", "X-BeenThere": "qemu-devel@nongnu.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "qemu development <qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org" }, "content": "From: \"Scott J. Goldman\" <scottjg@umich.edu>\n\nAdd the core Apple VFIO backend: an IOMMU container type and a PCI\ndevice type that together provide VFIO passthrough on macOS via the\nVFIOUserPCIDriver DriverKit extension.\n\nAppleVFIOContainer (container-apple.c):\n Implements VFIOIOMMUClass for DMA map/unmap by calling through to the\n dext client's register/unregister_dma functions. Synthesizes\n VFIO_DEVICE_GET_INFO responses with appropriate flags for the\n passthrough device.\n\nVFIOApplePCIDevice (apple-device.c):\n QOM type \"vfio-apple-pci\" subclassing VFIOPCIDevice. Provides the\n full VFIODeviceIOOps implementation:\n - Config space reads forwarded to the dext; writes filtered to block\n BAR and status register reprogramming (macOS/DART owns those).\n - PCI COMMAND register writes forwarded for bus-master/memory-space\n enable.\n - BAR regions directly mapped via IOConnectMapMemory64 and accessed\n as host MMIO loads/stores.\n - MSI/MSI-X interrupt delivery through a bitmap-poll model with\n async GCD notification bridged to an EventNotifier.\n - Device reset via the dext (IOPCIDevice::Reset FLR/hot-reset).\n - Shared dext connection management for multi-function devices.\n\napple.h defines the shared types: AppleVFIOContainer, AppleVFIOState,\nAppleVFIOBarMap, and VFIOApplePCIDevice.\n\nSigned-off-by: Scott J. Goldman <scottjgo@gmail.com>\n---\n hw/vfio/apple-device.c | 945 ++++++++++++++++++++++++++++++++++++++\n hw/vfio/apple.h | 74 +++\n hw/vfio/container-apple.c | 241 ++++++++++\n hw/vfio/meson.build | 6 +-\n 4 files changed, 1264 insertions(+), 2 deletions(-)\n create mode 100644 hw/vfio/apple-device.c\n create mode 100644 hw/vfio/apple.h\n create mode 100644 hw/vfio/container-apple.c", "diff": "diff --git a/hw/vfio/apple-device.c b/hw/vfio/apple-device.c\nnew file mode 100644\nindex 0000000000..9291ac845b\n--- /dev/null\n+++ b/hw/vfio/apple-device.c\n@@ -0,0 +1,945 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * Apple/macOS VFIO PCI device passthrough via DriverKit dext.\n+ *\n+ * Copyright (c) 2026 Scott J. Goldman\n+ */\n+\n+#include \"qemu/osdep.h\"\n+\n+#include <errno.h>\n+#include <string.h>\n+#include <unistd.h>\n+\n+#include <linux/vfio.h>\n+\n+#include \"apple-dext-client.h\"\n+#include \"hw/vfio/apple.h\"\n+#include \"hw/vfio/vfio-container.h\"\n+#include \"qapi/error.h\"\n+#include \"qemu/error-report.h\"\n+#include \"qemu/host-pci-mmio.h\"\n+#include \"qemu/main-loop.h\"\n+\n+typedef struct AppleVFIOSharedDext {\n+ io_connect_t conn;\n+ uint32_t refs;\n+} AppleVFIOSharedDext;\n+\n+typedef struct AppleVFIODMAProbe {\n+ uint64_t managed_bdf;\n+ uint64_t host_bus;\n+ uint64_t host_device;\n+ uint64_t host_function;\n+ DeviceState *match;\n+} AppleVFIODMAProbe;\n+\n+static GHashTable *apple_vfio_shared_dexts;\n+\n+static inline guint apple_vfio_dext_key(uint8_t bus, uint8_t device,\n+ uint8_t function)\n+{\n+ return ((guint)bus << 16) | ((guint)device << 8) | function;\n+}\n+\n+static inline AppleVFIOContainer *apple_vfio_container(VFIODevice *vbasedev)\n+{\n+ return VFIO_IOMMU_APPLE(vbasedev->bcontainer);\n+}\n+\n+static inline io_connect_t apple_vfio_connection(VFIODevice *vbasedev)\n+{\n+ AppleVFIOContainer *container = apple_vfio_container(vbasedev);\n+\n+ return container ? container->dext_conn : IO_OBJECT_NULL;\n+}\n+\n+static void apple_vfio_find_dma_companion_cb(PCIBus *bus, PCIDevice *pdev,\n+ void *opaque)\n+{\n+ AppleVFIODMAProbe *probe = opaque;\n+ Error *err = NULL;\n+ uint64_t managed_bdf;\n+ uint64_t host_bus;\n+ uint64_t host_device;\n+ uint64_t host_function;\n+\n+ if (probe->match ||\n+ !object_dynamic_cast(OBJECT(pdev), \"apple-dma-pci\")) {\n+ return;\n+ }\n+\n+ managed_bdf = object_property_get_uint(OBJECT(pdev), \"managed-bdf\", &err);\n+ if (err) {\n+ error_free(err);\n+ return;\n+ }\n+\n+ host_bus = object_property_get_uint(OBJECT(pdev), \"x-apple-host-bus\", &err);\n+ if (err) {\n+ error_free(err);\n+ return;\n+ }\n+\n+ host_device = object_property_get_uint(OBJECT(pdev),\n+ \"x-apple-host-device\", &err);\n+ if (err) {\n+ error_free(err);\n+ return;\n+ }\n+\n+ host_function = object_property_get_uint(OBJECT(pdev),\n+ \"x-apple-host-function\", &err);\n+ if (err) {\n+ error_free(err);\n+ return;\n+ }\n+\n+ if (managed_bdf == probe->managed_bdf &&\n+ host_bus == probe->host_bus &&\n+ host_device == probe->host_device &&\n+ host_function == probe->host_function) {\n+ probe->match = DEVICE(pdev);\n+ }\n+}\n+\n+static DeviceState *apple_vfio_find_dma_companion(VFIOApplePCIDevice *adev)\n+{\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+ PCIDevice *pdev = PCI_DEVICE(vdev);\n+ AppleVFIODMAProbe probe = {\n+ .managed_bdf = PCI_BUILD_BDF(pci_dev_bus_num(pdev), pdev->devfn),\n+ .host_bus = vdev->host.bus,\n+ .host_device = vdev->host.slot,\n+ .host_function = vdev->host.function,\n+ };\n+\n+ pci_for_each_device_under_bus(pci_device_root_bus(pdev),\n+ apple_vfio_find_dma_companion_cb, &probe);\n+ return probe.match;\n+}\n+\n+static void apple_vfio_signal_irqfd(int fd)\n+{\n+ static const uint64_t value = 1;\n+ ssize_t ret;\n+\n+ if (fd < 0) {\n+ return;\n+ }\n+\n+ do {\n+ ret = write(fd, &value, sizeof(value));\n+ } while (ret < 0 && errno == EINTR);\n+}\n+\n+static void apple_vfio_deliver_irq(VFIOPCIDevice *vdev, uint32_t vector)\n+{\n+ switch (vdev->interrupt) {\n+ case VFIO_INT_MSI:\n+ case VFIO_INT_MSIX:\n+ if (vector < vdev->nr_vectors && vdev->msi_vectors[vector].use) {\n+ apple_vfio_signal_irqfd(\n+ event_notifier_get_wfd(&vdev->msi_vectors[vector].interrupt));\n+ }\n+ break;\n+ case VFIO_INT_INTx:\n+ apple_vfio_signal_irqfd(\n+ event_notifier_get_wfd(&vdev->intx.interrupt));\n+ break;\n+ default:\n+ break;\n+ }\n+}\n+\n+/*\n+ * Called on a GCD dispatch queue when the dext signals pending interrupts.\n+ * Just pokes the EventNotifier to wake the QEMU main loop.\n+ */\n+static void apple_vfio_irq_wakeup(void *opaque)\n+{\n+ VFIOApplePCIDevice *adev = opaque;\n+\n+ event_notifier_set(&adev->apple->irq_notifier);\n+}\n+\n+/*\n+ * QEMU main-loop fd handler: drain the pending-interrupt bitfield from\n+ * the dext, deliver each flagged vector, then re-arm the async wait.\n+ */\n+static void apple_vfio_irq_handler(void *opaque)\n+{\n+ VFIOApplePCIDevice *adev = opaque;\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+ VFIODevice *vbasedev = &vdev->vbasedev;\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ AppleVFIOState *apple = adev->apple;\n+ uint64_t pending[4];\n+ int word;\n+\n+ if (!event_notifier_test_and_clear(&apple->irq_notifier)) {\n+ return;\n+ }\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ return;\n+ }\n+\n+ if (apple_dext_read_pending_irqs(conn, pending) != KERN_SUCCESS) {\n+ apple_dext_interrupt_notify_rearm(apple->irq_notify);\n+ return;\n+ }\n+\n+ for (word = 0; word < 4; word++) {\n+ uint64_t bits = pending[word];\n+\n+ while (bits) {\n+ int bit = __builtin_ctzll(bits);\n+ uint32_t vector = word * 64 + bit;\n+\n+ apple_vfio_deliver_irq(vdev, vector);\n+ bits &= bits - 1;\n+ }\n+ }\n+\n+ apple_dext_interrupt_notify_rearm(apple->irq_notify);\n+}\n+\n+bool apple_vfio_get_bar_info(VFIOApplePCIDevice *adev, uint8_t bar,\n+ uint8_t *mem_idx, uint64_t *size,\n+ uint8_t *type)\n+{\n+ io_connect_t conn = apple_vfio_connection(&VFIO_PCI_DEVICE(adev)->vbasedev);\n+\n+ if (conn != IO_OBJECT_NULL) {\n+ return apple_dext_get_bar_info(conn, bar, mem_idx, size, type) ==\n+ KERN_SUCCESS;\n+ }\n+\n+ if (mem_idx) {\n+ *mem_idx = 0;\n+ }\n+ if (size) {\n+ *size = 0;\n+ }\n+ if (type) {\n+ *type = 0;\n+ }\n+ return false;\n+}\n+\n+static void apple_vfio_pci_init(VFIOApplePCIDevice *adev)\n+{\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+\n+ /*\n+ * On macOS, HVF can only map on 16kb page boundaries, so these quirk\n+ * fixes end up breaking things. Likewise the performance enhancements\n+ * there rely on kvm-specific features. Disable for now, but we should\n+ * revisit this.\n+ */\n+ vdev->no_bar_quirks = true;\n+}\n+\n+static bool apple_vfio_pci_pre_realize(VFIOApplePCIDevice *adev, Error **errp)\n+{\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+ VFIODevice *vbasedev = &vdev->vbasedev;\n+\n+ adev->apple = g_new0(AppleVFIOState, 1);\n+\n+ if (!vbasedev->name) {\n+ vbasedev->name = g_strdup_printf(\"apple-%04x:%02x:%02x.%x\",\n+ vdev->host.domain,\n+ vdev->host.bus,\n+ vdev->host.slot,\n+ vdev->host.function);\n+ }\n+\n+ return true;\n+}\n+\n+static bool apple_vfio_create_dma_companion(VFIOApplePCIDevice *adev,\n+ Error **errp)\n+{\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+ PCIDevice *pdev = PCI_DEVICE(vdev);\n+ DeviceState *dev;\n+\n+ if (adev->dma_companion_autocreated && adev->dma_companion) {\n+ return true;\n+ }\n+\n+ if (apple_vfio_find_dma_companion(adev) != NULL) {\n+ return true;\n+ }\n+\n+ dev = qdev_new(\"apple-dma-pci\");\n+ if (!object_property_set_uint(OBJECT(dev), \"managed-bdf\",\n+ PCI_BUILD_BDF(pci_dev_bus_num(pdev),\n+ pdev->devfn), errp) ||\n+ !object_property_set_uint(OBJECT(dev), \"x-apple-host-bus\",\n+ vdev->host.bus, errp) ||\n+ !object_property_set_uint(OBJECT(dev), \"x-apple-host-device\",\n+ vdev->host.slot, errp) ||\n+ !object_property_set_uint(OBJECT(dev), \"x-apple-host-function\",\n+ vdev->host.function, errp)) {\n+ object_unref(OBJECT(dev));\n+ return false;\n+ }\n+\n+ if (!qdev_realize(dev, BUS(pci_get_bus(pdev)), errp)) {\n+ object_unref(OBJECT(dev));\n+ return false;\n+ }\n+\n+ adev->dma_companion = dev;\n+ adev->dma_companion_autocreated = true;\n+ object_unref(OBJECT(dev));\n+ return true;\n+}\n+\n+static void apple_vfio_destroy_dma_companion(VFIOApplePCIDevice *adev)\n+{\n+ if (!adev->dma_companion_autocreated || adev->dma_companion == NULL) {\n+ return;\n+ }\n+\n+ object_unparent(OBJECT(adev->dma_companion));\n+ adev->dma_companion = NULL;\n+ adev->dma_companion_autocreated = false;\n+}\n+\n+bool apple_vfio_device_setup(VFIOApplePCIDevice *adev, Error **errp)\n+{\n+ VFIODevice *vbasedev = &VFIO_PCI_DEVICE(adev)->vbasedev;\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ uint32_t num_vectors = 0;\n+ kern_return_t kr;\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ error_setg(errp, \"vfio-apple: missing dext connection\");\n+ return false;\n+ }\n+\n+ kr = apple_dext_setup_interrupts(conn, &num_vectors);\n+ if (kr != KERN_SUCCESS) {\n+ error_setg(errp, \"vfio-apple: failed to setup interrupts (kr=0x%x)\",\n+ kr);\n+ return false;\n+ }\n+\n+ adev->apple->num_irq_vectors = num_vectors;\n+\n+ if (event_notifier_init(&adev->apple->irq_notifier, false) < 0) {\n+ error_setg(errp, \"vfio-apple: failed to create IRQ event notifier\");\n+ return false;\n+ }\n+\n+ qemu_set_fd_handler(event_notifier_get_fd(&adev->apple->irq_notifier),\n+ apple_vfio_irq_handler, NULL, adev);\n+\n+ adev->apple->irq_notify =\n+ apple_dext_interrupt_notify_create(conn, apple_vfio_irq_wakeup, adev);\n+ if (!adev->apple->irq_notify) {\n+ error_setg(errp,\n+ \"vfio-apple: failed to create IRQ async notification\");\n+ qemu_set_fd_handler(\n+ event_notifier_get_fd(&adev->apple->irq_notifier),\n+ NULL, NULL, NULL);\n+ event_notifier_cleanup(&adev->apple->irq_notifier);\n+ return false;\n+ }\n+\n+ return true;\n+}\n+\n+void apple_vfio_device_cleanup(VFIOApplePCIDevice *adev)\n+{\n+ AppleVFIOState *apple = adev->apple;\n+\n+ if (!apple) {\n+ return;\n+ }\n+\n+ if (apple->irq_notify) {\n+ apple_dext_interrupt_notify_destroy(apple->irq_notify);\n+ apple->irq_notify = NULL;\n+\n+ qemu_set_fd_handler(event_notifier_get_fd(&apple->irq_notifier),\n+ NULL, NULL, NULL);\n+ event_notifier_cleanup(&apple->irq_notifier);\n+ }\n+}\n+\n+static int apple_vfio_device_feature(VFIODevice *vdev,\n+ struct vfio_device_feature *feat)\n+{\n+ return -ENOTTY;\n+}\n+\n+static int apple_vfio_device_reset(VFIODevice *vbasedev)\n+{\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ return -ENODEV;\n+ }\n+\n+ return apple_dext_reset_device(conn) == KERN_SUCCESS ? 0 : -EIO;\n+}\n+\n+static int apple_vfio_get_region_info(VFIODevice *vbasedev,\n+ struct vfio_region_info *info,\n+ int *fd)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ uint32_t index = info->index;\n+ uint64_t size = 0;\n+\n+ if (fd) {\n+ *fd = -1;\n+ }\n+\n+ memset((char *)info + offsetof(struct vfio_region_info, flags), 0,\n+ sizeof(*info) - offsetof(struct vfio_region_info, flags));\n+\n+ info->index = index;\n+ info->flags = VFIO_REGION_INFO_FLAG_READ | VFIO_REGION_INFO_FLAG_WRITE;\n+ info->offset = (uint64_t)index << 20;\n+\n+ switch (info->index) {\n+ case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX:\n+ if (!apple_vfio_get_bar_info(adev, info->index, NULL, &size, NULL)) {\n+ size = 0;\n+ }\n+ info->size = size;\n+ info->flags |= VFIO_REGION_INFO_FLAG_MMAP;\n+ break;\n+ case VFIO_PCI_CONFIG_REGION_INDEX:\n+ info->size = PCIE_CONFIG_SPACE_SIZE;\n+ break;\n+ case VFIO_PCI_ROM_REGION_INDEX:\n+ case VFIO_PCI_VGA_REGION_INDEX:\n+ info->size = 0;\n+ break;\n+ default:\n+ return -EINVAL;\n+ }\n+\n+ return 0;\n+}\n+\n+static int apple_vfio_get_irq_info(VFIODevice *vbasedev,\n+ struct vfio_irq_info *info)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+\n+ switch (info->index) {\n+ case VFIO_PCI_MSI_IRQ_INDEX:\n+ info->flags = VFIO_IRQ_INFO_EVENTFD;\n+ info->count = adev->apple->num_irq_vectors;\n+ break;\n+ case VFIO_PCI_MSIX_IRQ_INDEX:\n+ info->flags = VFIO_IRQ_INFO_EVENTFD | VFIO_IRQ_INFO_NORESIZE;\n+ info->count = vdev->msix ? vdev->msix->entries : 0;\n+ break;\n+ case VFIO_PCI_INTX_IRQ_INDEX:\n+ info->flags = VFIO_IRQ_INFO_EVENTFD;\n+ info->count = 1;\n+ break;\n+ case VFIO_PCI_ERR_IRQ_INDEX:\n+ case VFIO_PCI_REQ_IRQ_INDEX:\n+ /*\n+ * Apple dext passthrough has no kernel-side AER or device-request\n+ * notification currently; return count 0 to tell the core to skip\n+ * these.\n+ */\n+ info->flags = 0;\n+ info->count = 0;\n+ break;\n+ default:\n+ return -EINVAL;\n+ }\n+\n+ return 0;\n+}\n+\n+static void apple_vfio_update_irq_mask(VFIODevice *vbasedev)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ uint64_t mask[4] = {0};\n+ uint32_t i;\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ return;\n+ }\n+\n+ switch (vdev->interrupt) {\n+ case VFIO_INT_MSI:\n+ case VFIO_INT_MSIX:\n+ for (i = 0; i < vdev->nr_vectors; i++) {\n+ if (vdev->msi_vectors[i].use) {\n+ mask[i / 64] |= 1ULL << (i % 64);\n+ }\n+ }\n+ break;\n+ case VFIO_INT_INTx:\n+ mask[0] = 1;\n+ break;\n+ default:\n+ break;\n+ }\n+\n+ apple_dext_set_irq_mask(conn, mask);\n+}\n+\n+static int apple_vfio_set_irqs(VFIODevice *vbasedev, struct vfio_irq_set *irq)\n+{\n+ apple_vfio_update_irq_mask(vbasedev);\n+ return 0;\n+}\n+\n+static int apple_vfio_bar_read(VFIODevice *vbasedev, uint8_t nr, off_t off,\n+ uint32_t size, void *data)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ AppleVFIOBarMap *bm = &adev->apple->bar_maps[nr];\n+ const void *p;\n+ uint64_t value;\n+\n+ if (!bm->addr || off + size > bm->size) {\n+ error_report(\"vfio-apple: BAR%d read out of range or unmapped\", nr);\n+ return -EINVAL;\n+ }\n+\n+ if (size != 1 && size != 2 && size != 4 && size != 8) {\n+ return -EINVAL;\n+ }\n+\n+ p = (const char *)bm->addr + off;\n+ value = host_pci_ldn_le_p(p, size);\n+ memcpy(data, &value, size);\n+\n+ return size;\n+}\n+\n+static int apple_vfio_region_read(VFIODevice *vbasedev, uint8_t nr, off_t off,\n+ uint32_t size, void *data)\n+{\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ kern_return_t kr;\n+ uint32_t legacy_size = 0;\n+\n+ if (nr != VFIO_PCI_CONFIG_REGION_INDEX) {\n+ return apple_vfio_bar_read(vbasedev, nr, off, size, data);\n+ }\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ return -ENODEV;\n+ }\n+\n+ legacy_size = MIN(size, PCIE_CONFIG_SPACE_SIZE - off);\n+\n+ if (legacy_size == 1 || legacy_size == 2 || legacy_size == 4) {\n+ uint64_t value = 0;\n+\n+ kr = apple_dext_config_read(conn, off, legacy_size, &value);\n+ if (kr != KERN_SUCCESS) {\n+ return -EIO;\n+ }\n+\n+ memcpy(data, &value, legacy_size);\n+ if (legacy_size < size) {\n+ memset((uint8_t *)data + legacy_size, 0, size - legacy_size);\n+ }\n+ return size;\n+ }\n+\n+ kr = apple_dext_config_read_block(conn, off, data, legacy_size);\n+ if (kr != KERN_SUCCESS) {\n+ return -EIO;\n+ }\n+ if (legacy_size < size) {\n+ memset((uint8_t *)data + legacy_size, 0, size - legacy_size);\n+ }\n+ return size;\n+}\n+\n+static bool apple_vfio_config_write_is_safe(off_t off, uint32_t size)\n+{\n+ off_t end = off + size;\n+\n+ /*\n+ * Block writes that would reprogram the device's bus identity or\n+ * address decoders. macOS / DART owns those registers; touching\n+ * them from the guest breaks the IOKit mapping and the device\n+ * \"falls off the bus.\"\n+ *\n+ * Everything else (vendor capabilities, MSI/MSI-X, PCIe cap, etc.)\n+ * is forwarded.\n+ */\n+\n+ /* PCI_STATUS stays emulated/blocked */\n+ if (off < PCI_STATUS + 2 && end > PCI_STATUS) {\n+ return false;\n+ }\n+\n+ /* BAR0-BAR5 */\n+ if (off < PCI_BASE_ADDRESS_5 + 4 && end > PCI_BASE_ADDRESS_0) {\n+ return false;\n+ }\n+\n+ return true;\n+}\n+\n+static int apple_vfio_forward_command_write(io_connect_t conn, off_t off,\n+ uint32_t size, const void *data)\n+{\n+ const uint8_t *bytes = data;\n+ off_t end = off + size;\n+ off_t cmd_start = MAX(off, (off_t)PCI_COMMAND);\n+ off_t cmd_end = MIN(end, (off_t)(PCI_COMMAND + 2));\n+ off_t pos;\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ return -ENODEV;\n+ }\n+\n+ for (pos = cmd_start; pos < cmd_end; pos++) {\n+ uint64_t value = bytes[pos - off];\n+ kern_return_t kr = apple_dext_config_write(conn, pos, 1, value);\n+\n+ if (kr != KERN_SUCCESS) {\n+ return -EIO;\n+ }\n+ }\n+\n+ return 0;\n+}\n+\n+static int apple_vfio_bar_write(VFIODevice *vbasedev, uint8_t nr, off_t off,\n+ uint32_t size, void *data)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ AppleVFIOBarMap *bm = &adev->apple->bar_maps[nr];\n+ void *p;\n+ uint64_t value = 0;\n+\n+ if (!bm->addr || off + size > bm->size) {\n+ error_report(\"vfio-apple: BAR%d write out of range or unmapped\", nr);\n+ return -EINVAL;\n+ }\n+\n+ if (size != 1 && size != 2 && size != 4 && size != 8) {\n+ return -EINVAL;\n+ }\n+\n+ p = (char *)bm->addr + off;\n+ memcpy(&value, data, size);\n+ host_pci_stn_le_p(p, size, value);\n+\n+ return size;\n+}\n+\n+static int apple_vfio_region_write(VFIODevice *vbasedev, uint8_t nr, off_t off,\n+ uint32_t size, void *data, bool post)\n+{\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ uint64_t value = 0;\n+ kern_return_t kr;\n+ uint32_t legacy_size;\n+\n+ if (nr != VFIO_PCI_CONFIG_REGION_INDEX) {\n+ return apple_vfio_bar_write(vbasedev, nr, off, size, data);\n+ }\n+\n+ if (off < PCI_COMMAND + 2 && off + size > PCI_COMMAND) {\n+ int ret = apple_vfio_forward_command_write(conn, off, size, data);\n+\n+ if (ret) {\n+ return ret;\n+ }\n+\n+ if (off >= PCI_COMMAND && off + size <= PCI_COMMAND + 2) {\n+ return size;\n+ }\n+ }\n+\n+ if (!apple_vfio_config_write_is_safe(off, size)) {\n+ return size;\n+ }\n+\n+ if (conn == IO_OBJECT_NULL) {\n+ return -ENODEV;\n+ }\n+\n+ memcpy(&value, data, size);\n+ legacy_size = MIN(size, PCIE_CONFIG_SPACE_SIZE - off);\n+ if (!(legacy_size == 1 || legacy_size == 2 || legacy_size == 4)) {\n+ return -EINVAL;\n+ }\n+\n+ kr = apple_dext_config_write(conn, off, legacy_size, value);\n+ if (kr != KERN_SUCCESS) {\n+ return -EIO;\n+ }\n+ return size;\n+}\n+\n+static int apple_vfio_region_map(VFIODevice *vbasedev, VFIORegion *region);\n+static void apple_vfio_region_unmap(VFIODevice *vbasedev, VFIORegion *region);\n+\n+static int apple_vfio_region_map(VFIODevice *vbasedev, VFIORegion *region)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(adev);\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ int bar = region->nr;\n+ VFIOBAR *vbar;\n+ mach_vm_address_t local_addr = 0;\n+ mach_vm_size_t bar_size = 0;\n+ uint8_t bar_type = 0;\n+ kern_return_t kr;\n+ int i;\n+\n+ if (bar < VFIO_PCI_BAR0_REGION_INDEX || bar >= VFIO_PCI_ROM_REGION_INDEX) {\n+ return 0;\n+ }\n+\n+ vbar = &vdev->bars[bar];\n+\n+ if (conn == IO_OBJECT_NULL || !vbar->size || vbar->ioport) {\n+ return 0;\n+ }\n+\n+ if (bar > 0 && vdev->bars[bar - 1].mem64) {\n+ return 0;\n+ }\n+\n+ if (adev->apple->bar_maps[bar].addr != NULL) {\n+ return 0;\n+ }\n+\n+ kr = apple_dext_map_bar(conn, bar, &local_addr, &bar_size, &bar_type);\n+ if (kr != KERN_SUCCESS) {\n+ warn_report(\"vfio-apple: BAR%d map failed for %s: 0x%x\",\n+ bar, vbasedev->name, kr);\n+ return -EIO;\n+ }\n+\n+ if (bar_size > vbar->size) {\n+ bar_size = vbar->size;\n+ }\n+\n+ adev->apple->bar_maps[bar].addr = (void *)local_addr;\n+ adev->apple->bar_maps[bar].size = bar_size;\n+\n+ /*\n+ * Use the pre-computed mmap regions — already split around the MSI-X\n+ * table/PBA hole by vfio_pci_fixup_msix_region() during realize.\n+ * We just need to fill in the host pointers from our dext mapping.\n+ */\n+ for (i = 0; i < region->nr_mmaps; i++) {\n+ region->mmaps[i].mmap = (char *)local_addr + region->mmaps[i].offset;\n+ vfio_region_register_mmap(region, i);\n+ }\n+\n+ return 0;\n+}\n+\n+static void apple_vfio_region_unmap(VFIODevice *vbasedev, VFIORegion *region)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ io_connect_t conn = apple_vfio_connection(vbasedev);\n+ int bar = region->nr;\n+ AppleVFIOBarMap *bm;\n+ int i;\n+\n+ if (bar < VFIO_PCI_BAR0_REGION_INDEX || bar >= VFIO_PCI_ROM_REGION_INDEX) {\n+ return;\n+ }\n+\n+ bm = &adev->apple->bar_maps[bar];\n+\n+ for (i = 0; i < region->nr_mmaps; i++) {\n+ if (region->mmaps[i].mmap) {\n+ vfio_region_unregister_mmap(region, i);\n+ region->mmaps[i].mmap = NULL;\n+ }\n+ }\n+\n+ if (conn != IO_OBJECT_NULL && bm->addr != NULL) {\n+ apple_dext_unmap_bar(conn, bar, (mach_vm_address_t)bm->addr);\n+ }\n+\n+ bm->addr = NULL;\n+ bm->size = 0;\n+}\n+\n+VFIODeviceIOOps apple_vfio_device_io_ops = {\n+ .device_feature = apple_vfio_device_feature,\n+ .get_region_info = apple_vfio_get_region_info,\n+ .get_irq_info = apple_vfio_get_irq_info,\n+ .set_irqs = apple_vfio_set_irqs,\n+ .device_reset = apple_vfio_device_reset,\n+ .region_read = apple_vfio_region_read,\n+ .region_write = apple_vfio_region_write,\n+ .region_map = apple_vfio_region_map,\n+ .region_unmap = apple_vfio_region_unmap,\n+};\n+\n+bool apple_vfio_dext_publish(uint8_t bus, uint8_t device, uint8_t function,\n+ io_connect_t conn)\n+{\n+ AppleVFIOSharedDext *shared;\n+ guint key = apple_vfio_dext_key(bus, device, function);\n+\n+ if (!apple_vfio_shared_dexts) {\n+ apple_vfio_shared_dexts =\n+ g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);\n+ }\n+\n+ if (g_hash_table_lookup(apple_vfio_shared_dexts, GUINT_TO_POINTER(key))) {\n+ return false;\n+ }\n+\n+ shared = g_new0(AppleVFIOSharedDext, 1);\n+ shared->conn = conn;\n+ shared->refs = 1;\n+ g_hash_table_insert(apple_vfio_shared_dexts, GUINT_TO_POINTER(key), shared);\n+ return true;\n+}\n+\n+io_connect_t apple_vfio_dext_lookup(uint8_t bus, uint8_t device,\n+ uint8_t function)\n+{\n+ AppleVFIOSharedDext *shared;\n+ guint key = apple_vfio_dext_key(bus, device, function);\n+\n+ if (!apple_vfio_shared_dexts) {\n+ return IO_OBJECT_NULL;\n+ }\n+\n+ shared = g_hash_table_lookup(apple_vfio_shared_dexts,\n+ GUINT_TO_POINTER(key));\n+ if (!shared) {\n+ return IO_OBJECT_NULL;\n+ }\n+\n+ shared->refs++;\n+ return shared->conn;\n+}\n+\n+void apple_vfio_dext_release(uint8_t bus, uint8_t device, uint8_t function,\n+ io_connect_t conn)\n+{\n+ AppleVFIOSharedDext *shared;\n+ guint key = apple_vfio_dext_key(bus, device, function);\n+\n+ if (!apple_vfio_shared_dexts) {\n+ return;\n+ }\n+\n+ shared = g_hash_table_lookup(apple_vfio_shared_dexts,\n+ GUINT_TO_POINTER(key));\n+ if (!shared || shared->conn != conn) {\n+ return;\n+ }\n+\n+ if (--shared->refs == 0) {\n+ apple_dext_disconnect(conn);\n+ g_hash_table_remove(apple_vfio_shared_dexts, GUINT_TO_POINTER(key));\n+ }\n+}\n+\n+/* ------------------------------------------------------------------ */\n+/* QOM type: vfio-apple-pci */\n+/* ------------------------------------------------------------------ */\n+\n+static void (*parent_realize)(PCIDevice *, Error **);\n+static void (*parent_exit)(PCIDevice *);\n+\n+static void apple_vfio_pci_instance_init(Object *obj)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(obj);\n+\n+ apple_vfio_pci_init(adev);\n+}\n+\n+static void apple_vfio_pci_realize_fn(PCIDevice *pdev, Error **errp)\n+{\n+ ERRP_GUARD();\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(pdev);\n+\n+ if (!apple_vfio_pci_pre_realize(adev, errp)) {\n+ return;\n+ }\n+\n+ parent_realize(pdev, errp);\n+ if (*errp) {\n+ g_clear_pointer(&adev->apple, g_free);\n+ return;\n+ }\n+\n+ if (!apple_vfio_create_dma_companion(adev, errp)) {\n+ if (parent_exit) {\n+ parent_exit(pdev);\n+ }\n+ g_clear_pointer(&adev->apple, g_free);\n+ return;\n+ }\n+}\n+\n+static void apple_vfio_pci_exit_fn(PCIDevice *pdev)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(pdev);\n+\n+ apple_vfio_destroy_dma_companion(adev);\n+\n+ if (parent_exit) {\n+ parent_exit(pdev);\n+ }\n+}\n+\n+static void apple_vfio_pci_finalize_fn(Object *obj)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(obj);\n+\n+ apple_vfio_device_cleanup(adev);\n+ g_clear_pointer(&adev->apple, g_free);\n+}\n+\n+static void apple_vfio_pci_class_init(ObjectClass *klass, const void *data)\n+{\n+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(klass);\n+ DeviceClass *dc = DEVICE_CLASS(klass);\n+\n+ parent_realize = pdc->realize;\n+ parent_exit = pdc->exit;\n+\n+ pdc->realize = apple_vfio_pci_realize_fn;\n+ pdc->exit = apple_vfio_pci_exit_fn;\n+ dc->user_creatable = true;\n+ dc->desc = \"VFIO-based PCI device assignment (Apple/macOS)\";\n+}\n+\n+static const TypeInfo vfio_apple_pci_info = {\n+ .name = TYPE_VFIO_APPLE_PCI,\n+ .parent = TYPE_VFIO_PCI,\n+ .instance_size = sizeof(VFIOApplePCIDevice),\n+ .instance_init = apple_vfio_pci_instance_init,\n+ .instance_finalize = apple_vfio_pci_finalize_fn,\n+ .class_init = apple_vfio_pci_class_init,\n+};\n+\n+static void register_vfio_apple_pci_type(void)\n+{\n+ type_register_static(&vfio_apple_pci_info);\n+}\n+\n+type_init(register_vfio_apple_pci_type)\ndiff --git a/hw/vfio/apple.h b/hw/vfio/apple.h\nnew file mode 100644\nindex 0000000000..81d4bd2b66\n--- /dev/null\n+++ b/hw/vfio/apple.h\n@@ -0,0 +1,74 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * Apple/macOS VFIO passthrough common definitions.\n+ *\n+ * Copyright (c) 2026 Scott J. Goldman\n+ */\n+\n+#ifndef HW_VFIO_APPLE_H\n+#define HW_VFIO_APPLE_H\n+\n+#include <stdint.h>\n+\n+#include \"hw/vfio/pci.h\"\n+#include \"hw/vfio/vfio-container.h\"\n+#include \"qapi/error.h\"\n+#include \"qemu/event_notifier.h\"\n+\n+#ifdef CONFIG_DARWIN\n+#include <IOKit/IOKitLib.h>\n+#else\n+typedef uintptr_t io_connect_t;\n+#define IO_OBJECT_NULL ((io_connect_t)0)\n+#endif\n+\n+OBJECT_DECLARE_SIMPLE_TYPE(AppleVFIOContainer, VFIO_IOMMU_APPLE)\n+\n+struct AppleVFIOContainer {\n+ VFIOContainer parent_obj;\n+ io_connect_t dext_conn;\n+ uint8_t host_bus;\n+ uint8_t host_device;\n+ uint8_t host_function;\n+};\n+\n+typedef struct AppleDextInterruptNotify AppleDextInterruptNotify;\n+\n+typedef struct AppleVFIOBarMap {\n+ void *addr;\n+ size_t size;\n+} AppleVFIOBarMap;\n+\n+typedef struct AppleVFIOState {\n+ AppleDextInterruptNotify *irq_notify;\n+ EventNotifier irq_notifier;\n+ uint32_t num_irq_vectors;\n+ AppleVFIOBarMap bar_maps[PCI_ROM_SLOT];\n+} AppleVFIOState;\n+\n+OBJECT_DECLARE_SIMPLE_TYPE(VFIOApplePCIDevice, VFIO_APPLE_PCI)\n+\n+struct VFIOApplePCIDevice {\n+ VFIOPCIDevice parent_obj;\n+ AppleVFIOState *apple;\n+ DeviceState *dma_companion;\n+ bool dma_companion_autocreated;\n+};\n+\n+extern VFIODeviceIOOps apple_vfio_device_io_ops;\n+\n+bool apple_vfio_device_setup(VFIOApplePCIDevice *adev, Error **errp);\n+void apple_vfio_device_cleanup(VFIOApplePCIDevice *adev);\n+bool apple_vfio_get_bar_info(VFIOApplePCIDevice *adev, uint8_t bar,\n+ uint8_t *mem_idx, uint64_t *size,\n+ uint8_t *type);\n+\n+bool apple_vfio_dext_publish(uint8_t bus, uint8_t device, uint8_t function,\n+ io_connect_t conn);\n+io_connect_t apple_vfio_dext_lookup(uint8_t bus, uint8_t device,\n+ uint8_t function);\n+void apple_vfio_dext_release(uint8_t bus, uint8_t device, uint8_t function,\n+ io_connect_t conn);\n+\n+#endif /* HW_VFIO_APPLE_H */\ndiff --git a/hw/vfio/container-apple.c b/hw/vfio/container-apple.c\nnew file mode 100644\nindex 0000000000..5a5c55b622\n--- /dev/null\n+++ b/hw/vfio/container-apple.c\n@@ -0,0 +1,241 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * Apple/macOS VFIO IOMMU container backend.\n+ *\n+ * Copyright (c) 2026 Scott J. Goldman\n+ */\n+\n+#include \"qemu/osdep.h\"\n+\n+#include <linux/vfio.h>\n+\n+#include \"apple-dext-client.h\"\n+#include \"hw/vfio/apple.h\"\n+#include \"hw/vfio/vfio-device.h\"\n+#include \"hw/vfio/vfio-listener.h\"\n+#include \"qapi/error.h\"\n+#include \"system/ramblock.h\"\n+\n+static bool apple_vfio_setup(VFIOContainer *bcontainer, Error **errp)\n+{\n+ bcontainer->pgsizes = qemu_real_host_page_size();\n+ bcontainer->dma_max_mappings = UINT_MAX;\n+ bcontainer->dirty_pages_supported = false;\n+ bcontainer->max_dirty_bitmap_size = 0;\n+ bcontainer->dirty_pgsizes = 0;\n+ return true;\n+}\n+\n+/*\n+ * DMA map/unmap are no-ops: Apple passthrough handles DMA mapping through\n+ * the companion apple-dma-pci device which talks to the dext directly,\n+ * bypassing the IOMMU container's DMA path. The stubs are required because\n+ * the VFIO listener asserts they are non-NULL.\n+ */\n+static int apple_vfio_dma_map(const VFIOContainer *bcontainer, hwaddr iova,\n+ uint64_t size, void *vaddr, bool readonly,\n+ MemoryRegion *mr)\n+{\n+ return 0;\n+}\n+\n+static int apple_vfio_dma_unmap(const VFIOContainer *bcontainer, hwaddr iova,\n+ uint64_t size, IOMMUTLBEntry *iotlb,\n+ bool unmap_all)\n+{\n+ return 0;\n+}\n+\n+static int apple_vfio_set_dirty_page_tracking(const VFIOContainer *bcontainer,\n+ bool start, Error **errp)\n+{\n+ error_setg_errno(errp, ENOTSUP, \"vfio-apple does not support migration\");\n+ return -ENOTSUP;\n+}\n+\n+static int apple_vfio_query_dirty_bitmap(const VFIOContainer *bcontainer,\n+ VFIOBitmap *vbmap, hwaddr iova,\n+ hwaddr size, uint64_t backend_flag,\n+ Error **errp)\n+{\n+ error_setg_errno(errp, ENOTSUP, \"vfio-apple does not support migration\");\n+ return -ENOTSUP;\n+}\n+\n+static int apple_vfio_pci_hot_reset(VFIODevice *vbasedev, bool single)\n+{\n+ return 0;\n+}\n+\n+static AppleVFIOContainer *apple_vfio_container_connect(AddressSpace *as,\n+ VFIODevice *vbasedev,\n+ Error **errp)\n+{\n+ VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(vbasedev->dev);\n+ AppleVFIOContainer *container;\n+ VFIOContainer *bcontainer;\n+ VFIOAddressSpace *space;\n+ VFIOIOMMUClass *vioc;\n+ int ret;\n+\n+ space = vfio_address_space_get(as);\n+ container = VFIO_IOMMU_APPLE(object_new(TYPE_VFIO_IOMMU_APPLE));\n+ bcontainer = VFIO_IOMMU(container);\n+ vioc = VFIO_IOMMU_GET_CLASS(bcontainer);\n+\n+ container->host_bus = vdev->host.bus;\n+ container->host_device = vdev->host.slot;\n+ container->host_function = vdev->host.function;\n+\n+ ret = ram_block_uncoordinated_discard_disable(true);\n+ if (ret) {\n+ error_setg_errno(errp, -ret, \"Cannot set discarding of RAM broken\");\n+ goto fail_unref;\n+ }\n+\n+ container->dext_conn = apple_dext_connect(container->host_bus,\n+ container->host_device,\n+ container->host_function);\n+ if (container->dext_conn == IO_OBJECT_NULL) {\n+ error_setg(errp,\n+ \"vfio-apple: could not connect to dext for host PCI \"\n+ \"%02x:%02x.%x\",\n+ container->host_bus, container->host_device,\n+ container->host_function);\n+ goto fail_discards;\n+ }\n+\n+ if (apple_dext_claim(container->dext_conn) != KERN_SUCCESS) {\n+ error_setg(errp,\n+ \"vfio-apple: failed to claim dext-backed PCI device \"\n+ \"%02x:%02x.%x\",\n+ container->host_bus, container->host_device,\n+ container->host_function);\n+ goto fail_release_conn;\n+ }\n+\n+ if (!apple_vfio_dext_publish(container->host_bus, container->host_device,\n+ container->host_function,\n+ container->dext_conn)) {\n+ error_setg(errp,\n+ \"vfio-apple: duplicate dext owner for host PCI %02x:%02x.%x\",\n+ container->host_bus, container->host_device,\n+ container->host_function);\n+ goto fail_release_conn;\n+ }\n+\n+ if (!vioc->setup(bcontainer, errp)) {\n+ goto fail_shared_conn;\n+ }\n+\n+ vfio_address_space_insert(space, bcontainer);\n+\n+ if (!vfio_listener_register(bcontainer, errp)) {\n+ goto fail_address_space;\n+ }\n+\n+ bcontainer->initialized = true;\n+ return container;\n+\n+fail_address_space:\n+ vfio_listener_unregister(bcontainer);\n+ QLIST_REMOVE(bcontainer, next);\n+ bcontainer->space = NULL;\n+fail_shared_conn:\n+ apple_vfio_dext_release(container->host_bus, container->host_device,\n+ container->host_function, container->dext_conn);\n+ container->dext_conn = IO_OBJECT_NULL;\n+fail_discards:\n+ ram_block_uncoordinated_discard_disable(false);\n+fail_unref:\n+ object_unref(container);\n+ vfio_address_space_put(space);\n+ return NULL;\n+\n+fail_release_conn:\n+ apple_dext_disconnect(container->dext_conn);\n+ container->dext_conn = IO_OBJECT_NULL;\n+ goto fail_discards;\n+}\n+\n+static void apple_vfio_container_disconnect(AppleVFIOContainer *container)\n+{\n+ VFIOContainer *bcontainer = VFIO_IOMMU(container);\n+ VFIOAddressSpace *space = bcontainer->space;\n+\n+ ram_block_uncoordinated_discard_disable(false);\n+ vfio_listener_unregister(bcontainer);\n+\n+ apple_vfio_dext_release(container->host_bus, container->host_device,\n+ container->host_function, container->dext_conn);\n+ container->dext_conn = IO_OBJECT_NULL;\n+\n+ object_unref(container);\n+ vfio_address_space_put(space);\n+}\n+\n+static bool apple_vfio_attach_device(const char *name, VFIODevice *vbasedev,\n+ AddressSpace *as, Error **errp)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ AppleVFIOContainer *container;\n+ struct vfio_device_info info = {\n+ .argsz = sizeof(info),\n+ .flags = VFIO_DEVICE_FLAGS_PCI | VFIO_DEVICE_FLAGS_RESET,\n+ .num_regions = VFIO_PCI_NUM_REGIONS,\n+ .num_irqs = VFIO_PCI_NUM_IRQS,\n+ };\n+\n+ container = apple_vfio_container_connect(as, vbasedev, errp);\n+ if (!container) {\n+ return false;\n+ }\n+\n+ vbasedev->fd = -1;\n+ vbasedev->io_ops = &apple_vfio_device_io_ops;\n+ vfio_device_prepare(vbasedev, VFIO_IOMMU(container), &info);\n+\n+ if (!apple_vfio_device_setup(adev, errp)) {\n+ vfio_device_unprepare(vbasedev);\n+ apple_vfio_container_disconnect(container);\n+ return false;\n+ }\n+\n+ return true;\n+}\n+\n+static void apple_vfio_detach_device(VFIODevice *vbasedev)\n+{\n+ VFIOApplePCIDevice *adev = VFIO_APPLE_PCI(vbasedev->dev);\n+ AppleVFIOContainer *container = VFIO_IOMMU_APPLE(vbasedev->bcontainer);\n+\n+ apple_vfio_device_cleanup(adev);\n+ vfio_device_unprepare(vbasedev);\n+ apple_vfio_container_disconnect(container);\n+}\n+\n+static void vfio_iommu_apple_class_init(ObjectClass *klass, const void *data)\n+{\n+ VFIOIOMMUClass *vioc = VFIO_IOMMU_CLASS(klass);\n+\n+ vioc->setup = apple_vfio_setup;\n+ vioc->dma_map = apple_vfio_dma_map;\n+ vioc->dma_unmap = apple_vfio_dma_unmap;\n+ vioc->attach_device = apple_vfio_attach_device;\n+ vioc->detach_device = apple_vfio_detach_device;\n+ vioc->set_dirty_page_tracking = apple_vfio_set_dirty_page_tracking;\n+ vioc->query_dirty_bitmap = apple_vfio_query_dirty_bitmap;\n+ vioc->pci_hot_reset = apple_vfio_pci_hot_reset;\n+}\n+\n+static const TypeInfo apple_vfio_types[] = {\n+ {\n+ .name = TYPE_VFIO_IOMMU_APPLE,\n+ .parent = TYPE_VFIO_IOMMU,\n+ .instance_size = sizeof(AppleVFIOContainer),\n+ .class_init = vfio_iommu_apple_class_init,\n+ },\n+};\n+\n+DEFINE_TYPES(apple_vfio_types)\ndiff --git a/hw/vfio/meson.build b/hw/vfio/meson.build\nindex 965c8e5b80..473f8669f9 100644\n--- a/hw/vfio/meson.build\n+++ b/hw/vfio/meson.build\n@@ -40,6 +40,8 @@ system_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files(\n # Apple VFIO backend\n if host_os == 'darwin'\n system_ss.add(when: 'CONFIG_VFIO',\n- if_true: [files('apple-dext-client.c'),\n- coref, iokit])\n+ if_true: [files('apple-device.c',\n+ 'container-apple.c',\n+ 'apple-dext-client.c'),\n+ coref, iokit])\n endif\n", "prefixes": [ "RFC", "08/10" ] }