From nobody Sat Apr 11 19:53:55 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1775374270; cv=none; d=zohomail.com; s=zohoarc; b=f9B97pdTYcnxLvtaw2iVAuwNi6NnPnKVBN6N0BU1F8aSpbKePLh64bKtWRxm8tRd8ujk8CC+8HQv+4S2CLqSDvas/4px3+IdwYzKVEN/aZVQrld8Xt1gbPECWV1GttdNFP7H8nZxQhqtu69TEv1CTBMomMcYaZ9zFmvZz0xTuHw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1775374270; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=zxfini/03ZCMwgjgBQ4pisEMx2q4rGRX8zkQlXe0tR4=; b=juu/LAK5LDP9qDkHXXQiBFI2HKU/OBXLCPOb7zbu0krheT/9BmGWuPWLj6t7Wz9aMQNXNjRZg21JdSyHLmGsE7Nuk5nZ4XzoV/UFzAjHa75LMJKX2H6R7gZLWFglT9s5PnOOITdj0NVDv+Qa7hRGujYE9GWMTZrwIKzMATxVwHI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1775374270277600.2086372222857; Sun, 5 Apr 2026 00:31:10 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w9HvT-0003yA-0N; Sun, 05 Apr 2026 03:29:43 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1w9HvQ-0003xj-Mo for qemu-devel@nongnu.org; Sun, 05 Apr 2026 03:29:40 -0400 Received: from mail-dy1-x1330.google.com ([2607:f8b0:4864:20::1330]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1w9HvN-0007Nf-QK for qemu-devel@nongnu.org; Sun, 05 Apr 2026 03:29:40 -0400 Received: by mail-dy1-x1330.google.com with SMTP id 5a478bee46e88-2c156c4a9efso3546438eec.1 for ; Sun, 05 Apr 2026 00:29:37 -0700 (PDT) Received: from localhost.localdomain ([2601:645:8200:47:41e4:ff2b:ff70:4d75]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2cb92ea0ef1sm7636502eec.21.2026.04.05.00.29.34 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 05 Apr 2026 00:29:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775374176; x=1775978976; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=zxfini/03ZCMwgjgBQ4pisEMx2q4rGRX8zkQlXe0tR4=; b=NB8bg2WYH+z/AmCtJCvRXkos/w/gGzR/p8f4znh4dQsYnSWEU66n7Vu6oSknX9iSWf 74fv6g01TFcAk/fh2N1yjSNNLNisdlpO6e7aoKgKJ/aFoWe5zg5rwoKCSKy/PkxS04C9 gyCFgP00D5JehrGqcOTwzLdpiUE+CMKP5R+7bPxbHfDn1fIWUbvN/9rhFhSkmnEP9H70 WtZZCDgVNd2duPPDeCRExel0icXuGTAd09jqjXyQ93eQWWOoAsELkn5eM4Rc7s90V6ns yyEToP3N/pPObSesmhDS0kgq+IOoAiwQSNRLph7ScLBNOh3n0+lpo532VU0K/gwAq2YB H7pg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775374176; x=1775978976; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=zxfini/03ZCMwgjgBQ4pisEMx2q4rGRX8zkQlXe0tR4=; b=VV1NTNLPXOdjIGJk+WMnicuiokkaXwulKFCzW1+8kCbvooif0Y2R6oPNwc8qoMqSxJ +hvD1OR86Wt2oEyaQB1efXrRY+uyR/RjyEG6Eo4g4en6UqLhr5MqHXRSA/LnRO+Mhtn9 Lf4RLNEvh3Iy4YG2vINvkynCIwYWYXC9VTEyZIhKBSRU8hm79GEQOIRw/gpCMmDaGhj/ hjyFp1LheOMGzPqB94fXIGwy71K9DdL7qFM4NiI2B9S+qAeCeS1X22ZrB8MD0C9JKNiR xkk5OYR7DQLqNtCkGIBSY2F5qRkGS7PQkEtLTW+5/brzShtBcaMzm399OPcj3L5ZOnd5 S8ug== X-Gm-Message-State: AOJu0YweX1qPYYIZTiu1m6aBJfGWc9NXGGpPqty+kGakmmOsJpn6LtgY sWiBIbxfW1HFuCosSzZhh7UzNoF1ws4ZLxJqyEagszgLmsI8RamnjWFEF8ALkhyNy2c= X-Gm-Gg: AeBDietDqHRNB86d8jEc4skl6er5lAkiOkv/e9tOr3EwYO6ttIrPMKWbQTocmFaEsw9 MndWujHnceNtZMqrZYS1aXiVmgKjPFyNRtF9kzLNEnGxpkNoildu40+enpToSdCcdFDPBZ2hwYA Wqug7NqhzTj1V+j8wjSnWsCWgeJWwunrknvAs1dqZoC3Q2kf60ipM5oLBG+ki6j1r2GXOVwF2pW 4LsNXBnr/7E8CtTsXj/4q17KqGZb8EbCxX/B+2i2Ss8MCdF/7+pwQtaqtlQ9FVMlGzPqkLj52tT sVJi5XnvswGrp9rkQX94R7tRA2W5aKozTxNEEVwvM76F4J3otTZvFUJwg7YDdFn3KSmVRso9wxK SIIbYd99imKFhSl1jJwJUYi0zmcQToCzSnfudpOKUF6NVJjF5E9VyKoEOIZU7+MtKehGddRx8y7 9+ZL/g/t29Pet8PB1j1nhLfm6PpqW/uoZC89wngB7wZ6u1aXXVBjQdORtLcx0kP97S+MvoRjY3e ZW39jN2U3/L/QcxiOmIANWrmqo= X-Received: by 2002:a05:7301:3f0f:b0:2c8:7172:3b88 with SMTP id 5a478bee46e88-2cbfc16ec3bmr3959627eec.28.1775374175786; Sun, 05 Apr 2026 00:29:35 -0700 (PDT) From: "Scott J. Goldman" To: qemu-devel@nongnu.org Cc: alex@shazbot.org, clg@redhat.com, pbonzini@redhat.com, rbolshakov@ddn.com, phil@philjordan.eu, mst@redhat.com, john.levon@nutanix.com, thanos.makatos@nutanix.com, qemu-s390x@nongnu.org, "Scott J. Goldman" , "Scott J. Goldman" Subject: [RFC PATCH 07/10] vfio/apple: Add DriverKit dext client library Date: Sun, 5 Apr 2026 00:28:51 -0700 Message-ID: <20260405072857.66484-8-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: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1330; envelope-from=scottjgo@gmail.com; helo=mail-dy1-x1330.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, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, 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 List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1775374272097154100 From: "Scott J. Goldman" Add the C client library for communicating with the VFIOUserPCIDriver DriverKit extension (dext) on macOS. This provides the low-level IOUserClient wrappers that the Apple VFIO backend will use: - Connection management (connect, disconnect, claim) - PCI config space read/write (individual and block) - BAR info queries, BAR mapping/unmapping, MMIO read/write - DMA region registration and unregistration - Interrupt setup, pending IRQ polling, async notification - Device reset (FLR with hot-reset fallback) All calls go through IOKit's IOConnectCallScalarMethod / IOConnectMapMemory64 to the dext, which mediates access to the physical PCI device via PCIDriverKit. Signed-off-by: Scott J. Goldman --- hw/vfio/apple-dext-client.c | 681 ++++++++++++++++++++++++++++++++++++ hw/vfio/apple-dext-client.h | 253 ++++++++++++++ hw/vfio/meson.build | 7 + 3 files changed, 941 insertions(+) create mode 100644 hw/vfio/apple-dext-client.c create mode 100644 hw/vfio/apple-dext-client.h diff --git a/hw/vfio/apple-dext-client.c b/hw/vfio/apple-dext-client.c new file mode 100644 index 0000000000..7ba03fc6e9 --- /dev/null +++ b/hw/vfio/apple-dext-client.c @@ -0,0 +1,681 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * C client implementation for communicating with the VFIOUserPCIDriver de= xt + * via IOKit IOUserClient. + * + * Copyright (c) 2026 Scott J. Goldman + */ + +#include "qemu/osdep.h" + +#include "apple-dext-client.h" + +#include +#include +#include +#include + +enum { + kSelectorGetIdentity =3D 0, + kSelectorClaim =3D 1, + kSelectorTerminate =3D 2, + kSelectorAllocateDMABuffer =3D 3, + kSelectorFreeDMABuffer =3D 4, + kSelectorRegisterDMARegion =3D 5, + kSelectorUnregisterDMARegion =3D 6, + kSelectorProbeDMARegion =3D 7, + kSelectorConfigRead =3D 8, + kSelectorConfigWrite =3D 9, + kSelectorGetBARInfo =3D 10, + kSelectorMMIORead =3D 11, + kSelectorMMIOWrite =3D 12, + kSelectorSetupInterrupts =3D 13, + kSelectorCheckInterrupt =3D 14, + kSelectorWaitInterrupt =3D 15, + kSelectorSetIRQMask =3D 16, + kSelectorResetDevice =3D 17, +}; + +/* + * Keep this in sync with PCIDriverKit BAR type encoding. Bit 3 indicates + * prefetchability for memory BARs. + */ +#define APPLE_DEXT_BAR_PREFETCHABLE_MASK 0x08 +#ifndef kIOMapWriteCombineCache +#define kIOMapWriteCombineCache 0x00000400 +#endif + +static bool +dext_service_matches_class(io_service_t service, const char *className) +{ + bool match =3D false; + CFTypeRef ref; + + ref =3D IORegistryEntryCreateCFProperty(service, CFSTR("IOUserClass"), + kCFAllocatorDefault, 0); + if (ref =3D=3D NULL) { + return false; + } + + if (CFGetTypeID(ref) =3D=3D CFStringGetTypeID()) { + CFStringRef expected =3D CFStringCreateWithCString( + kCFAllocatorDefault, className, kCFStringEncodingUTF8); + if (expected !=3D NULL) { + match =3D CFStringCompare((CFStringRef)ref, expected, 0) + =3D=3D kCFCompareEqualTo; + CFRelease(expected); + } + } + CFRelease(ref); + return match; +} + +static bool +dext_connection_matches_bdf(io_connect_t connection, + uint8_t bus, uint8_t device, uint8_t function) +{ + uint64_t output[6] =3D {0}; + uint32_t outputCount =3D 6; + kern_return_t kr; + + kr =3D IOConnectCallMethod(connection, kSelectorGetIdentity, + NULL, 0, NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS || outputCount < 3) { + return false; + } + + return (uint8_t)output[0] =3D=3D bus && + (uint8_t)output[1] =3D=3D device && + (uint8_t)output[2] =3D=3D function; +} + +io_connect_t +apple_dext_connect(uint8_t bus, uint8_t device, uint8_t function) +{ + CFMutableDictionaryRef matching; + io_iterator_t iterator =3D IO_OBJECT_NULL; + io_connect_t result =3D IO_OBJECT_NULL; + io_service_t service; + kern_return_t kr; + + matching =3D IOServiceMatching("IOUserService"); + if (matching =3D=3D NULL) { + return IO_OBJECT_NULL; + } + + kr =3D IOServiceGetMatchingServices(kIOMainPortDefault, matching, &ite= rator); + if (kr !=3D KERN_SUCCESS) { + return IO_OBJECT_NULL; + } + + while ((service =3D IOIteratorNext(iterator)) !=3D IO_OBJECT_NULL) { + io_connect_t connection =3D IO_OBJECT_NULL; + + if (!dext_service_matches_class(service, "VFIOUserPCIDriver")) { + IOObjectRelease(service); + continue; + } + + kr =3D IOServiceOpen(service, mach_task_self(), 0, &connection); + IOObjectRelease(service); + + if (kr !=3D KERN_SUCCESS) { + continue; + } + + if (dext_connection_matches_bdf(connection, bus, device, function)= ) { + result =3D connection; + break; + } + + IOServiceClose(connection); + } + + IOObjectRelease(iterator); + return result; +} + +void +apple_dext_disconnect(io_connect_t connection) +{ + if (connection !=3D IO_OBJECT_NULL) { + IOServiceClose(connection); + } +} + +kern_return_t +apple_dext_claim(io_connect_t connection) +{ + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + return IOConnectCallMethod(connection, + kSelectorClaim, + NULL, 0, NULL, 0, + NULL, NULL, NULL, NULL); +} + +kern_return_t +apple_dext_register_dma(io_connect_t connection, + uint64_t iova, + uint64_t client_va, + uint64_t size, + uint64_t *out_bus_addr, + uint64_t *out_bus_len) +{ + uint64_t input[3] =3D { iova, client_va, size }; + uint64_t output[3] =3D {0}; + uint32_t outputCount =3D 3; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorRegisterDMARegion, + input, 3, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + if (out_bus_addr !=3D NULL && outputCount >=3D 2) { + *out_bus_addr =3D output[1]; + } + if (out_bus_len !=3D NULL && outputCount >=3D 3) { + *out_bus_len =3D output[2]; + } + + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_unregister_dma(io_connect_t connection, + uint64_t iova) +{ + uint64_t input[1] =3D { iova }; + + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + return IOConnectCallMethod(connection, + kSelectorUnregisterDMARegion, + input, 1, + NULL, 0, + NULL, NULL, + NULL, NULL); +} + +kern_return_t +apple_dext_probe_dma(io_connect_t connection, + uint64_t iova, + uint64_t offset, + uint64_t *out_word) +{ + uint64_t input[2] =3D { iova, offset }; + uint64_t output[1] =3D {0}; + uint32_t outputCount =3D 1; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL || out_word =3D=3D NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorProbeDMARegion, + input, 2, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + *out_word =3D output[0]; + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_config_read(io_connect_t connection, + uint64_t offset, + uint64_t width, + uint64_t *out_value) +{ + uint64_t input[2] =3D { offset, width }; + uint64_t output[1] =3D {0}; + uint32_t outputCount =3D 1; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL || out_value =3D=3D NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorConfigRead, + input, 2, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + *out_value =3D output[0]; + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_config_write(io_connect_t connection, + uint64_t offset, + uint64_t width, + uint64_t value) +{ + uint64_t input[3] =3D { offset, width, value }; + + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + return IOConnectCallMethod(connection, + kSelectorConfigWrite, + input, 3, + NULL, 0, + NULL, NULL, + NULL, NULL); +} + +kern_return_t +apple_dext_config_read_block(io_connect_t connection, + uint64_t offset, + void *buf, + size_t len) +{ + uint8_t *dst =3D (uint8_t *)buf; + uint64_t pos =3D offset; + size_t remaining =3D len; + + if (connection =3D=3D IO_OBJECT_NULL || buf =3D=3D NULL) { + return kIOReturnBadArgument; + } + + while (remaining >=3D 4) { + uint64_t val =3D 0; + uint32_t dword; + kern_return_t kr; + + kr =3D apple_dext_config_read(connection, pos, 4, &val); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + dword =3D (uint32_t)val; + memcpy(dst, &dword, 4); + dst +=3D 4; + pos +=3D 4; + remaining -=3D 4; + } + + while (remaining > 0) { + uint64_t val =3D 0; + kern_return_t kr; + + kr =3D apple_dext_config_read(connection, pos, 1, &val); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + *dst =3D (uint8_t)val; + dst++; + pos++; + remaining--; + } + + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_get_bar_info(io_connect_t connection, + uint8_t bar, + uint8_t *out_mem_idx, + uint64_t *out_size, + uint8_t *out_type) +{ + uint64_t input[1] =3D { bar }; + uint64_t output[3] =3D {0}; + uint32_t outputCount =3D 3; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorGetBARInfo, + input, 1, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + if (out_mem_idx !=3D NULL) { + *out_mem_idx =3D (uint8_t)output[0]; + } + if (out_size !=3D NULL) { + *out_size =3D output[1]; + } + if (out_type !=3D NULL) { + *out_type =3D (uint8_t)output[2]; + } + + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_map_bar(io_connect_t connection, + uint8_t bar, + mach_vm_address_t *out_addr, + mach_vm_size_t *out_size, + uint8_t *out_type) +{ + uint64_t bar_size =3D 0; + uint8_t bar_type =3D 0; + uint32_t mem_type; + mach_vm_address_t addr =3D 0; + mach_vm_size_t size =3D 0; + IOOptionBits opts =3D kIOMapAnywhere; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL || out_addr =3D=3D NULL || out_si= ze =3D=3D NULL) { + return kIOReturnBadArgument; + } + + kr =3D apple_dext_get_bar_info(connection, bar, NULL, + &bar_size, &bar_type); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + /* + * The memory type for IOConnectMapMemory64 must match the dext's + * CopyClientMemoryForType expectation: + * kVFIOUserPCIDriverUserClientMemoryTypeBAR0 (=3D 1) plus the BAR ind= ex. + * This is NOT the same as the PCIDriverKit internal memoryIndex retur= ned + * by GetBARInfo. + */ + mem_type =3D 1 + (uint32_t)bar; + + if (bar_type & APPLE_DEXT_BAR_PREFETCHABLE_MASK) { + opts |=3D kIOMapWriteCombineCache; + } + + kr =3D IOConnectMapMemory64(connection, mem_type, mach_task_self(), + &addr, &size, opts); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + *out_addr =3D addr; + *out_size =3D size; + if (out_type !=3D NULL) { + *out_type =3D bar_type; + } + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_unmap_bar(io_connect_t connection, + uint8_t bar, + mach_vm_address_t addr) +{ + uint32_t mem_type =3D 1 + (uint32_t)bar; + + if (connection =3D=3D IO_OBJECT_NULL || addr =3D=3D 0) { + return kIOReturnBadArgument; + } + + return IOConnectUnmapMemory64(connection, mem_type, mach_task_self(), = addr); +} + +kern_return_t +apple_dext_mmio_read(io_connect_t connection, + uint8_t mem_idx, + uint64_t offset, + uint64_t width, + uint64_t *out_value) +{ + uint64_t input[3] =3D { mem_idx, offset, width }; + uint64_t output[1] =3D {0}; + uint32_t outputCount =3D 1; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL || out_value =3D=3D NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorMMIORead, + input, 3, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + *out_value =3D output[0]; + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_mmio_write(io_connect_t connection, + uint8_t mem_idx, + uint64_t offset, + uint64_t width, + uint64_t value) +{ + uint64_t input[4] =3D { mem_idx, offset, width, value }; + + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + return IOConnectCallMethod(connection, + kSelectorMMIOWrite, + input, 4, + NULL, 0, + NULL, NULL, + NULL, NULL); +} + +kern_return_t +apple_dext_setup_interrupts(io_connect_t connection, + uint32_t *out_num_vectors) +{ + uint64_t output[1] =3D {0}; + uint32_t outputCount =3D 1; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorSetupInterrupts, + NULL, 0, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + if (out_num_vectors !=3D NULL && outputCount >=3D 1) { + *out_num_vectors =3D (uint32_t)output[0]; + } + + return kIOReturnSuccess; +} + +kern_return_t +apple_dext_reset_device(io_connect_t connection) +{ + if (connection =3D=3D IO_OBJECT_NULL) { + return kIOReturnBadArgument; + } + + return IOConnectCallMethod(connection, + kSelectorResetDevice, + NULL, 0, NULL, 0, + NULL, NULL, NULL, NULL); +} + +kern_return_t +apple_dext_set_irq_mask(io_connect_t connection, const uint64_t mask[4]) +{ + if (connection =3D=3D IO_OBJECT_NULL || mask =3D=3D NULL) { + return kIOReturnBadArgument; + } + + return IOConnectCallMethod(connection, + kSelectorSetIRQMask, + mask, 4, + NULL, 0, + NULL, NULL, + NULL, NULL); +} + +kern_return_t +apple_dext_read_pending_irqs(io_connect_t connection, uint64_t pending[4]) +{ + uint64_t output[4] =3D {0}; + uint32_t outputCount =3D 4; + kern_return_t kr; + uint32_t i; + + if (connection =3D=3D IO_OBJECT_NULL || pending =3D=3D NULL) { + return kIOReturnBadArgument; + } + + kr =3D IOConnectCallMethod(connection, + kSelectorCheckInterrupt, + NULL, 0, + NULL, 0, + output, &outputCount, + NULL, NULL); + if (kr !=3D KERN_SUCCESS) { + return kr; + } + + for (i =3D 0; i < 4; i++) { + pending[i] =3D (i < outputCount) ? output[i] : 0; + } + + return kIOReturnSuccess; +} + +struct AppleDextInterruptNotify { + io_connect_t connection; + IONotificationPortRef notifyPort; + mach_port_t machPort; + dispatch_queue_t dispatchQueue; + void (*handler_fn)(void *opaque); + void *opaque; +}; + +static void +apple_dext_async_callback(void *refcon, IOReturn result, + void **args, uint32_t numArgs) +{ + AppleDextInterruptNotify *notify =3D refcon; + + if (result =3D=3D kIOReturnSuccess && notify->handler_fn) { + notify->handler_fn(notify->opaque); + } +} + +static kern_return_t +apple_dext_interrupt_notify_arm(AppleDextInterruptNotify *notify) +{ + uint64_t asyncRef[kIOAsyncCalloutCount]; + + asyncRef[kIOAsyncCalloutFuncIndex] =3D + (uint64_t)(uintptr_t)apple_dext_async_callback; + asyncRef[kIOAsyncCalloutRefconIndex] =3D + (uint64_t)(uintptr_t)notify; + + return IOConnectCallAsyncMethod(notify->connection, + kSelectorWaitInterrupt, + notify->machPort, + asyncRef, kIOAsyncCalloutCount, + NULL, 0, NULL, 0, + NULL, NULL, NULL, NULL); +} + +AppleDextInterruptNotify * +apple_dext_interrupt_notify_create(io_connect_t connection, + void (*handler_fn)(void *opaque), + void *opaque) +{ + AppleDextInterruptNotify *notify; + kern_return_t kr; + + if (connection =3D=3D IO_OBJECT_NULL || handler_fn =3D=3D NULL) { + return NULL; + } + + notify =3D g_new0(AppleDextInterruptNotify, 1); + notify->connection =3D connection; + notify->handler_fn =3D handler_fn; + notify->opaque =3D opaque; + + notify->notifyPort =3D IONotificationPortCreate(kIOMainPortDefault); + if (!notify->notifyPort) { + g_free(notify); + return NULL; + } + + notify->dispatchQueue =3D dispatch_queue_create( + "org.qemu.vfio-apple.irq-notify", DISPATCH_QUEUE_SERIAL); + IONotificationPortSetDispatchQueue(notify->notifyPort, + notify->dispatchQueue); + notify->machPort =3D IONotificationPortGetMachPort(notify->notifyPort); + + kr =3D apple_dext_interrupt_notify_arm(notify); + if (kr !=3D KERN_SUCCESS) { + IONotificationPortDestroy(notify->notifyPort); + dispatch_release(notify->dispatchQueue); + g_free(notify); + return NULL; + } + + return notify; +} + +kern_return_t +apple_dext_interrupt_notify_rearm(AppleDextInterruptNotify *notify) +{ + if (!notify) { + return kIOReturnBadArgument; + } + return apple_dext_interrupt_notify_arm(notify); +} + +void +apple_dext_interrupt_notify_destroy(AppleDextInterruptNotify *notify) +{ + if (!notify) { + return; + } + + IONotificationPortDestroy(notify->notifyPort); + dispatch_release(notify->dispatchQueue); + g_free(notify); +} diff --git a/hw/vfio/apple-dext-client.h b/hw/vfio/apple-dext-client.h new file mode 100644 index 0000000000..07574493e6 --- /dev/null +++ b/hw/vfio/apple-dext-client.h @@ -0,0 +1,253 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * C API for connecting to the VFIOUserPCIDriver DriverKit extension. + * + * The vfio-user server process uses this to: + * 1. Find and open an IOUserClient to the dext for a given PCI BDF. + * 2. Claim the device so the dext opens its IOPCIDevice provider. + * 3. Register client-owned memory (QEMU guest RAM mapped via shared file) + * for DMA by the physical PCI device. + * 4. Unregister DMA regions when QEMU removes them. + * + * Integration with libvfio-user: + * vfu_dma_register_cb_t -> apple_dext_register_dma() + * vfu_dma_unregister_cb_t -> apple_dext_unregister_dma() + * + * Copyright (c) 2026 Scott J. Goldman + */ + +#ifndef HW_VFIO_APPLE_DEXT_CLIENT_H +#define HW_VFIO_APPLE_DEXT_CLIENT_H + +#include +#include + +/* + * Find the VFIOUserPCIDriver dext instance matching the given PCI BDF + * and open an IOUserClient connection to it. + * Returns IO_OBJECT_NULL on failure. + */ +io_connect_t apple_dext_connect(uint8_t bus, uint8_t device, + uint8_t function); + +/* + * Close a previously opened connection. + */ +void apple_dext_disconnect(io_connect_t connection); + +/* + * Claim the PCI device through the dext (opens the IOPCIDevice provider). + * Must be called before registering DMA regions. + */ +kern_return_t apple_dext_claim(io_connect_t connection); + +/* + * Register a region of this process's address space for DMA. + * + * @iova: guest IOVA (device-visible DMA address) + * @client_va: virtual address of the memory in this process + * @size: region size in bytes + * @out_bus_addr: receives first DMA bus address segment (may be NULL) + * @out_bus_len: receives first DMA bus address segment length (may be NU= LL) + * + * The memory at client_va must remain valid and mapped until the region + * is unregistered. + */ +kern_return_t apple_dext_register_dma(io_connect_t connection, + uint64_t iova, + uint64_t client_va, + uint64_t size, + uint64_t *out_bus_addr, + uint64_t *out_bus_len); + +/* + * Unregister a previously registered DMA region identified by its IOVA. + */ +kern_return_t apple_dext_unregister_dma(io_connect_t connection, + uint64_t iova); + +/* + * Read 8 bytes from a registered DMA region's IOMemoryDescriptor. + * Used to verify the descriptor references the same physical pages + * as the client's virtual mapping. + * + * @iova: base IOVA of the registered region + * @offset: byte offset within the region to read from + * @out_word: receives the 8-byte value read from the descriptor + */ +kern_return_t apple_dext_probe_dma(io_connect_t connection, + uint64_t iova, + uint64_t offset, + uint64_t *out_word); + +/* + * Read from PCI configuration space. + * + * @offset: byte offset into config space + * @width: access width in bytes (1, 2, or 4) + * @out_value: receives the value read + */ +kern_return_t apple_dext_config_read(io_connect_t connection, + uint64_t offset, + uint64_t width, + uint64_t *out_value); + +/* + * Write to PCI configuration space. + * + * @offset: byte offset into config space + * @width: access width in bytes (1, 2, or 4) + * @value: value to write + */ +kern_return_t apple_dext_config_write(io_connect_t connection, + uint64_t offset, + uint64_t width, + uint64_t value); + +/* + * Read a contiguous block of PCI configuration space. + * Internally issues repeated 32-bit reads, with a final + * narrower read for any trailing bytes. + * + * @offset: starting byte offset + * @buf: destination buffer + * @len: number of bytes to read + */ +kern_return_t apple_dext_config_read_block(io_connect_t connection, + uint64_t offset, + void *buf, + size_t len); + +/* + * Query BAR information from the PCI device. + * + * @bar: BAR index (0-5) + * @out_mem_idx: receives the memory index for MemoryRead/Write calls + * @out_size: receives the BAR size in bytes + * @out_type: receives the BAR type (mem32, mem64, io, etc.) + */ +kern_return_t apple_dext_get_bar_info(io_connect_t connection, + uint8_t bar, + uint8_t *out_mem_idx, + uint64_t *out_size, + uint8_t *out_type); + +/* + * Map a PCI BAR directly into this process through the dext. + * + * The dext supplies the BAR's IOMemoryDescriptor and IOKit applies the + * appropriate cache mode for the BAR type (default-cache for BAR0 style + * register windows, write-combine for prefetchable apertures). + * + * @bar: BAR index (0-5) + * @out_addr: receives the mapped virtual address + * @out_size: receives the mapped size + * @out_type: receives the BAR type (may be NULL) + */ +kern_return_t apple_dext_map_bar(io_connect_t connection, + uint8_t bar, + mach_vm_address_t *out_addr, + mach_vm_size_t *out_size, + uint8_t *out_type); + +/* + * Unmap a BAR previously mapped with apple_dext_map_bar(). + */ +kern_return_t apple_dext_unmap_bar(io_connect_t connection, + uint8_t bar, + mach_vm_address_t addr); + +/* + * Read from a PCI BAR (MMIO). + * + * @mem_idx: memory index from apple_dext_get_bar_info + * @offset: byte offset within the BAR + * @width: access width in bytes (1, 2, 4, or 8) + * @out_value: receives the value read + */ +kern_return_t apple_dext_mmio_read(io_connect_t connection, + uint8_t mem_idx, + uint64_t offset, + uint64_t width, + uint64_t *out_value); + +/* + * Write to a PCI BAR (MMIO). + * + * @mem_idx: memory index from apple_dext_get_bar_info + * @offset: byte offset within the BAR + * @width: access width in bytes (1, 2, 4, or 8) + * @value: value to write + */ +kern_return_t apple_dext_mmio_write(io_connect_t connection, + uint8_t mem_idx, + uint64_t offset, + uint64_t width, + uint64_t value); + +/* + * Set up interrupt forwarding for the PCI device. + * Creates IOInterruptDispatchSource handlers for all available + * MSI/MSI-X vectors in the dext. Interrupts are queued in a ring + * buffer and retrieved via apple_dext_check_interrupt(). + * + * @out_num_vectors: receives the number of interrupt vectors registered + */ +kern_return_t apple_dext_setup_interrupts(io_connect_t connection, + uint32_t *out_num_vectors); + +/* + * Reset the PCI device via the dext. Tries FLR first, then falls + * back to PM reset (D3hot =E2=86=92 D0 transition). + */ +kern_return_t apple_dext_reset_device(io_connect_t connection); + +/* + * Set the IRQ enable mask in the dext. Only vectors with their + * corresponding bit set will be recorded as pending when the + * hardware fires. mask[] is 4 x uint64_t covering 256 vectors. + */ +kern_return_t apple_dext_set_irq_mask(io_connect_t connection, + const uint64_t mask[4]); + +/* + * Read and clear all pending interrupt bits from the dext. + * Returns up to 256 bits (4 MSI/MSI-X vectors per bit) across + * 4 uint64_t words. Each bit that was set is atomically cleared + * in the dext. + */ +kern_return_t apple_dext_read_pending_irqs(io_connect_t connection, + uint64_t pending[4]); + +/* + * Opaque state for async interrupt notification from the dext. + */ +typedef struct AppleDextInterruptNotify AppleDextInterruptNotify; + +/* + * Create async interrupt notification. handler_fn is called on a GCD + * dispatch queue whenever the dext signals that one or more interrupt + * bits have been set. The handler should wake the QEMU main loop, + * which then calls apple_dext_read_pending_irqs() to drain the bits. + * + * The notification is armed immediately upon creation. + */ +AppleDextInterruptNotify * +apple_dext_interrupt_notify_create(io_connect_t connection, + void (*handler_fn)(void *opaque), + void *opaque); + +/* + * Re-arm the async interrupt notification after draining pending bits. + * Must be called after each wakeup to receive subsequent notifications. + */ +kern_return_t +apple_dext_interrupt_notify_rearm(AppleDextInterruptNotify *notify); + +/* + * Tear down and free async interrupt notification state. + */ +void apple_dext_interrupt_notify_destroy(AppleDextInterruptNotify *notify); + +#endif /* HW_VFIO_APPLE_DEXT_CLIENT_H */ diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build index 1ee9c11d5b..965c8e5b80 100644 --- a/hw/vfio/meson.build +++ b/hw/vfio/meson.build @@ -36,3 +36,10 @@ system_ss.add(when: 'CONFIG_IOMMUFD', if_false: files('i= ommufd-stubs.c')) system_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files( 'display.c', )) + +# Apple VFIO backend +if host_os =3D=3D 'darwin' + system_ss.add(when: 'CONFIG_VFIO', + if_true: [files('apple-dext-client.c'), + coref, iokit]) +endif --=20 2.50.1 (Apple Git-155)