Post

Replies

Boosts

Views

Activity

DEXT receives zero-filled buffer from DMA, despite firmware confirming data write
Hello everyone, I am migrating a KEXT for a SCSI PCI RAID controller (LSI 3108 RoC) to DriverKit (DEXT). While the DEXT loads successfully, I'm facing a DMA issue: an INQUIRY command results in a 0-byte disk because the data buffer received by the DEXT is all zeros, despite our firmware logs confirming that the correct data was prepared and sent. We have gathered detailed forensic evidence and would appreciate any insights from the community. Detailed Trace of a Failing INQUIRY Command: 1, DEXT Dispatches the Command: Our UserProcessParallelTask implementation correctly receives the INQUIRY task. Logs show the requested transfer size is 6 bytes, and the DEXT obtains the IOVA (0x801c0000) to pass to the hardware. DEXT Log: [UserProcessParallelTask_Impl] --- FORENSIC ANALYSIS --- [UserProcessParallelTask_Impl] fBufferIOVMAddr = 0x801c0000 [UserProcessParallelTask_Impl] fRequestedTransferCount = 6 2, Firmware Receives IOVA and Prepares Correct Data: A probe in our firmware confirms that the hardware successfully received the correct IOVA and the 6-byte length requirement. The firmware then prepares the correct 6-byte INQUIRY response in its internal staging buffer. Firmware Logs: -- [FIRMWARE PROBE: INCOMING DMA DUMP] -- Host IOVA (High:Low) = 0x00000000801c0000 DataLength in Header = 6 (0x6) --- [Firmware Outgoing Data Dump from go_inquiry] --- Source Address: 0x228BB800, Length: 6 bytes 0x0000: 00 00 05 12 1F 00 3, Hardware Reports a Successful Transfer, but Data is Lost: After the firmware initiates the DMA write to the Host IOVA, the hardware reports a successful transfer of 6 bytes back to our DEXT. DEXT Completion Log: [AME_Host_Normal_Handler_SCSI_Request] [TaskID: 200] COMPLETING... [AME_Host_Normal_Handler_SCSI_Request] Hardware Transferred = 6 bytes [AME_Host_Normal_Handler_SCSI_Request] - ReplyStatus = SUCCESS (0x0) [AME_Host_Normal_Handler_SCSI_Request] - SCSIStatus = SUCCESS (0x0) The Core Contradiction: Despite the firmware preparing the correct data and the hardware reporting a successful DMA transfer, the fDataBuffer in our DEXT remains filled with zeros. The 6 bytes of data are lost somewhere between the PCIe bus and host memory. This "data-in-firmware, zeros-in-DEXT" phenomenon leads us to believe the issue lies in memory address translation or a system security policy, as our legacy KEXT works perfectly on the same hardware. Compared to a KEXT, are there any known, stricter IOMMU/security policies for a DEXT that could cause this kind of "silent write failure" (even with a correct IOVA)? Alternatively, what is the correct and complete expected workflow in DriverKit for preparing an IOMemoryDescriptor* fDataBuffer (received in UserProcessParallelTask) for a PCI hardware device to use as a DMA write target? Any official documentation, examples, or advice on the IOMemoryDescriptor to PCI Bus Address workflow would be immensely helpful. Thank you. Charles
5
0
1.8k
Aug ’26
[DEXT Migration Issue] IOUserSCSIParallelInterfaceController fails to handle low-level I/O from `diskutil`
Hello everyone, We are migrating our KEXT for a Thunderbolt storage device to a DEXT based on IOUserSCSIParallelInterfaceController. We've run into a fundamental issue where the driver's behavior splits based on the I/O source: high-level I/O from the file system (e.g., Finder, cp) is mostly functional (with a minor ls -al sorting issue for Traditional Chinese filenames), while low-level I/O directly to the block device (e.g., diskutil) fails or acts unreliably. Basic read/write with dd appears to be mostly functional. We suspect that our DEXT is failing to correctly register its full device "personality" with the I/O Kit framework, unlike its KEXT counterpart. As a result, low-level I/O requests with special attributes (like cache synchronization) sent by diskutil are not being handled correctly by the IOUserSCSIParallelInterfaceController framework of our DEXT. Actions Performed & Relevant Logs 1. Discrepancy: diskutil info Shows Different Device Identities for DEXT vs. KEXT For the exact same hardware, the KEXT and DEXT are identified by the system as two different protocols. KEXT Environment: Device Identifier: disk5 Protocol: Fibre Channel Interface ... Disk Size: 66.0 TB Device Block Size: 512 Bytes DEXT Environment: Device Identifier: disk5 Protocol: SCSI SCSI Domain ID: 2 SCSI Target ID: 0 ... Disk Size: 66.0 TB Device Block Size: 512 Bytes 2. Divergent I/O Behavior: Partial Success with Finder/cp vs. Failure with diskutil High-Level I/O (Partially Successful): In the DEXT environment, if we operate on an existing volume (e.g., /Volumes/MyVolume), file copy operations using Finder or cp succeed. Furthermore, the logs we've placed in our single I/O entry point, UserProcessParallelTask_Impl, are triggered. Side Effect: However, running ls -al on such a volume shows an incorrect sorting order for files with Traditional Chinese names (they appear before . and ..). Low-Level I/O (Contradictory Behavior): In the DEXT environment, when we operate directly on the raw block device (/dev/disk5): diskutil partitionDisk ... -> Fails 100% of the time with the error: Error: -69825: Wiping volume data to prevent future accidental probing failed. dd command -> Basic read/write operations appear to work correctly (a write can be immediately followed by a read within the same DEXT session, and the data is correct). 3. Evidence of Cache Synchronization Failure (Non-deterministic Behavior) The success of the dd command is not deterministic. Cross-environment tests prove that its write operations are unreliable: First Test: In the DEXT environment, write a file with random data to /dev/disk5 using dd. Reboot into the KEXT environment. Read the data back from /dev/disk5 using dd. The result is a file filled with all zeros. Conclusion: The write operation only went to the hardware cache, and the data was lost upon reboot. Second Test: In the DEXT environment, write the same random file to /dev/disk5 using dd. Key Variable: Immediately after, still within the DEXT environment, read the data back once for verification. The content is correct! Reboot into the KEXT environment. Read the data back from /dev/disk5. This time, the content is correct! Conclusion: The additional read operation in the second test unintentionally triggered a hardware cache flush. This proves that the dd (in our DEXT) write operation by itself does not guarantee synchronization, making its behavior unreliable. Our Problem Based on the observations above, we have the conclusion: High-Level Path (triggered by Finder/cp): When an I/O request originates from the high-level file system, the framework seems to enter a fully-featured mode. In this mode, all SCSI commands, including READ/WRITE, INQUIRY, and SYNCHRONIZE CACHE, are correctly packaged and dispatched to our UserProcessParallelTask_Impl entry point. Therefore, Finder operations are mostly functional. Low-Level Path (triggered by dd/diskutil): When an I/O request originates from the low-level raw block device layer: The most basic READ/WRITE commands can be dispatched (which is why dd appears to work). However, critical management commands, such as INQUIRY and SYNCHRONIZE CACHE, are not being correctly dispatched or handled. This leads to the incorrect device identification in diskutil info and the failure of diskutil partitionDisk due to its inability to confirm cache synchronization. We would greatly appreciate any guidance, suggestions, or insights on how to resolve this discrepancy. Specifically, what is the recommended approach within DriverKit to ensure that a DEXT based on IOUserSCSIParallelInterfaceController can properly declare its capabilities and handle both high-level and low-level I/O requests uniformly? Thank you. Charles
12
0
2.5k
Jul ’26
System Panic with IOUserSCSIParallelInterfaceController during Dispatch Queue Configuration
Hello everyone, We are in the process of migrating a high-performance storage KEXT to DriverKit. During our initial validation phase, we noticed a performance gap between the DEXT and the KEXT, which prompted us to try and optimize our I/O handling process. Background and Motivation: Our test hardware is a RAID 0 array of two HDDs. According to AJA System Test, our legacy KEXT achieves a write speed of about 645 MB/s on this hardware, whereas the new DEXT reaches about 565 MB/s. We suspect the primary reason for this performance gap might be that the DEXT, by default, uses a serial work-loop to submit I/O commands, which fails to fully leverage the parallelism of the hardware array. Therefore, to eliminate this bottleneck and improve performance, we configured a dedicated parallel dispatch queue (MyParallelIOQueue) for the UserProcessParallelTask method. However, during our implementation attempt, we encountered a critical issue that caused a system-wide crash. The Operation Causing the Panic: We configured MyParallelIOQueue using the following combination of methods: In the .iig file: We appended the QUEUENAME(MyParallelIOQueue) macro after the override keyword of the UserProcessParallelTask method declaration. In the .cpp file: We manually created a queue with the same name by calling the IODispatchQueue::Create() function within our UserInitializeController method. The Result: This results in a macOS kernel panic during the DEXT loading process, forcing the user to perform a hard reboot. After the reboot, checking with the systemextensionsctl list command reveals the DEXT's status as [activated waiting for user], which indicates that it encountered an unrecoverable, fatal error during its initialization. Key Code Snippets to Reproduce the Panic: In .iig file - this was our exact implementation: class DRV_MAIN_CLASS_NAME: public IOUserSCSIParallelInterfaceController { public: virtual kern_return_t UserProcessParallelTask(...) override QUEUENAME(MyParallelIOQueue); }; In .h file: struct DRV_MAIN_CLASS_NAME_IVars { // ... IODispatchQueue* MyParallelIOQueue; }; In UserInitializeController implementation: kern_return_t IMPL(DRV_MAIN_CLASS_NAME, UserInitializeController) { // ... // We also included code to manually create the queue. kern_return_t ret = IODispatchQueue::Create("MyParallelIOQueue", kIODispatchQueueReentrant, 0, &ivars->MyParallelIOQueue); if (ret != kIOReturnSuccess) { // ... error handling ... } // ... return kIOReturnSuccess; } Our Question: What is the officially recommended and most stable method for configuring UserProcessParallelTask_Impl() to use a parallel I/O queue? Clarifying this is crucial for all developers pursuing high-performance storage solutions with DriverKit. Any explanation or guidance would be greatly appreciated. Best Regards, Charles
28
0
5.3k
Jun ’26
Optimizing SCSI HBA Constraints and Alignment for DriverKit on Apple Silicon
Hi Kevin, I'm starting this new thread to focus on alignment optimization and recalibrating our HBA constraints. Following up on your suggestion about UserReportHBAConstraints and alignment optimization, here are our current DEXT settings: Via UserReportHBAConstraints(): kIOMaximumSegmentCountRead/WriteKey: 129 kIOMaximumSegmentByteCountRead/WriteKey: 65,536 (64 KB) kIOMinimumSegmentAlignmentByteCountKey: 4 bytes kIOMaximumSegmentAddressableBitCountKey: 32 kIOMinimumHBADataAlignmentMaskKey: 0 Via SetProperties() (additional injection): kIOMaximumByteCountRead/WriteKey: 524,288 (512 KB) kIOMaximumBlockCountRead/WriteKey: 1,024 We inherited the segment count (129) and max I/O length (512 KB) from our legacy KEXT, which were originally calculated based on a 4 KB segment size (Max I/O 512 KB / 4 KB + 1 = 129). The current alignment value of 4 was essentially a placeholder, as the legacy hardware didn't enforce strict page-level alignment. Given that our testing is on Apple Silicon, we are considering increasing kIOMinimumSegmentAlignmentByteCountKey to 16,384 (16 KB) to match the native page size. However, I have two specific questions regarding this: Stripe Size vs. Page Size: Our RAID stripe size is typically larger than 16 KB (e.g., 64 KB or 128 KB). Should we be aligning the system to the RAID stripe size for hardware efficiency, or is it more critical to stick to the 16 KB page size to optimize the IOMMU/DART mapping overhead in DriverKit? Recalibration: If we increase the alignment to 16 KB, should we also adjust the kIOMaximumSegmentByteCount to match (i.e., 16 KB), or is it better to keep it at 64 KB to allow fewer, larger segments per I/O? We suspect that the 38% gain we saw in 4 KB Random Reads might improve even further if we fix this alignment bottleneck. Looking forward to your thoughts. Best regards, Charles
4
0
1.9k
Jun ’26
Xcode Signing Fails: Provisioning Profile "doesn't match" com.apple.developer.driverkit.userclient-access entitlement
Hello everyone, I am migrating a legacy KEXT to a DriverKit (DEXT) architecture. While the DEXT itself is working correctly, I am completely blocked by a code signing issue when trying to establish the UserClient connection from our SwiftUI management app. Project Goal & Status: Our DEXT (com.accusys.Acxxx.driver) activates successfully (systemextensionsctl list confirms [activated enabled]). The core functionality is working (diskutil list shows the corresponding disk device node). The Core Problem: The userclient-access Signing Error To allow the app to connect to the DEXT, the com.apple.developer.driverkit.userclient-access entitlement is required in the app's .entitlements file. However, as soon as this entitlement is added, the build fails. Both automatic and manual signing fail with the same error: `Provisioning profile ... doesn't match the entitlements file's value for the ... userclient-access entitlement.` This build failure prevents the generation of an .app bundle, making it impossible to inspect the final entitlements with codesign. What We've Confirmed: The necessary capabilities (like DriverKit Communicates with Drivers) are visible and enabled for our App ID on the developer portal. The issue persists on a clean system state and on the latest macOS Sequoia 15.7.1. Our Research and Hypothesis: We have reviewed the official documentation "Diagnosing issues with entitlements" (TN3125). According to the documentation, a "doesn't match" error implies a discrepancy between the entitlements file and the provisioning profile. Given that we have tried both automatic and manual profiles (after enabling the capability online), our hypothesis is that the provisioning profile generation process on Apple's backend is not correctly including the approved userclient-access entitlement into the profile file itself. The build fails because Xcode correctly detects this discrepancy. Our Questions: Did we misunderstand a step in the process, or is the issue not with the entitlement request at all? Alternatively, are there any other modifications we can make to successfully connect our App to the DEXT and trigger NewUserClient? Thank you for any guidance.
11
0
2.0k
May ’26
Kernel Panic: Power state transition (0 -> 2) timeout during DriverKit (DEXT) load sequence (IOUserSCSIParallelInterfaceController)
Hi Everyone, We are currently migrating a mature legacy KEXT to DriverKit for our PCIe SCSI storage controller (connected via Thunderbolt 3). During the DEXT load sequence, we have observed that the system automatically triggers a power state transition from State 0 (Off) to State 2 (On). However, this process results in a Kernel Panic due to a timeout after approximately 21 seconds. We have verified that our implementation of Start_Impl, UserInitializeController_Impl, and SetPowerState_Impl executes extremely fast, with a total execution time of less than one second. Specifically, SetPowerState_Impl returns kIOReturnSuccess immediately upon being called. Furthermore, our current Info.plist does not contain any IOPowerManagement dictionary or related keys. Despite the fast execution and the absence of explicit power management declarations in the plist, the kernel power management state machine (IOServicePM) still generates a 21-second timeout, leading to the following panic: Panic Log: panic(cpu 7 caller 0xfffffe0020be8fec): MySCSIDriver::setPowerState(0xfffffe2fb1a65c00 : 0xfffffe0020bfed88, 0 -> 2) timed out after 21257 ms @IOServicePM.cpp:5609 com.example.driver.dext: ( id: com.example.driver.dext; path: /Library/SystemExtensions/[UUID]/com.example.driver.dext; state: loaded ) Note on Previous Discussion: I would like to express my gratitude to Kevin from Apple DTS for the helpful discussion regarding the implementation of BundleParallelTask on the forums. Since then, we have shifted our development focus toward completing the overall management ecosystem, delivering a comprehensive operational interface for users, and handling specific user environments and behaviors. Our current priority is ensuring system stability—specifically resolving these Thunderbolt-related power management issues (sleep/wake)—to prepare the product for upcoming testing. I remain very grateful for the guidance provided on batch task optimization and intend to resume those optimizations once this critical stability baseline is secured. Technical Guidance Needed for PM Migration In our legacy KEXT, we utilized PMinit(), registerPowerDriver(), and joinPMtree() to precisely control the timing of power management registration. In transitioning to the DriverKit SDK, we have not found clear guidance on several key points: Standardized Migration Path: What is the recommended way to implement equivalent power management initialization (formerly PMinit) within a DriverKit subclass? In DriverKit, how should we replicate the behavior of manually calling registerPowerDriver and joinPMtree to ensure the driver is only monitored once the hardware is ready? Implicit Power Registration: Why does the system enforce a setPowerState(0 -> 2) transition on a subclass of IOUserSCSIParallelInterfaceController even when no IOPowerManagement dictionary is defined in the Info.plist? Is this a default behavior of the SCSI or PCI transport framework? Thunderbolt Specifics: Are there specific power proxying requirements or configurations for PCIe devices over Thunderbolt to avoid conflicts with the default IOPCIFamily power policies? Best Regards, Charles
3
0
1.6k
Mar ’26
[DriverKit SCSI] SCSI probe stalls for Target ID > 0 with IOUserSCSIParallelInterfaceController
Hello everyone, We are migrating a KEXT storage driver to DriverKit. In our KEXT, we use a "one LUN = one Target" model and successfully create multiple targets in a loop during initialization. We are now trying to replicate this architecture in our DEXT. The issue is that only Target 0 is fully probed and mounted. For Target 1, the lifecycle silently stops after the first TEST UNIT READY command is successfully acknowledged. The macOS SCSI layer never sends any subsequent probe commands (like INQUIRY) to this target. The failure sequence for Target 1, observed from our logs (regardless of whether Target 0 is created), is as follows: AsyncCreateTargetForID(1) -> UserInitializeTargetForID(1) (Succeeds) UserProcessParallelTask(Target: 1, Opcode: TUR) (Succeeds) The DEXT correctly acknowledges the TUR command for Target 1 by returning kSCSITaskStatus_CHECK_CONDITION with UNIT ATTENTION in the Sense Data (Succeeds) <-- Breakpoint --> UserProcessParallelTask(Target: 1, Opcode: INQUIRY) (Never happens) Through log comparison, we have confirmed that the DEXT's response to the TUR command for Target 1 is identical to the successful KEXT's response. We have tried creating only Target 1 (skipping Target 0 entirely), but the behavior is exactly the same -> the probe still stalls after the TUR. We initially suspected a race condition caused by consecutive calls to AsyncCreateTargetForID(). We attempted several methods to ensure that targets are created sequentially, such as trying to build a "creation chain" using OSAction completion handlers. However, these attempts were unsuccessful due to various compilation errors and API misunderstandings. In any case, this "race condition" theory was ultimately disproven by our experiment where creating only Target 1 still resulted in failure. We would like to ask two questions: Is our inability to have a Target ID greater than 0 fully probed by macOS a bug in our own code, or could there be another reason we are unaware of? If we do indeed need a "one-after-another" creation mechanism for AsyncCreateTargetForID, what is the correct way to implement a "chained creation" using OSAction completion handlers in DriverKit? Thank you for any help or guidance. Best Regards, Charles
3
0
1.4k
Jan ’26
DEXT (IOUserSCSIParallelInterfaceController): Direct I/O Succeeds, but Buffered I/O Fails with Data Corruption on Large File Copies
Hi all, We are migrating a SCSI HBA driver from KEXT to DriverKit (DEXT), with our DEXT inheriting from IOUserSCSIParallelInterfaceController. We've encountered a data corruption issue that is reliably reproducible under specific conditions and are hoping for some assistance from the community. Hardware and Driver Configuration: Controller: LSI 3108 DEXT Configuration: We are reporting our hardware limitations to the framework via the UserReportHBAConstraints function, with the following key settings: // UserReportHBAConstraints... addConstraint(kIOMaximumSegmentAddressableBitCountKey, 0x20); // 32-bit addConstraint(kIOMaximumSegmentCountWriteKey, 129); addConstraint(kIOMaximumByteCountWriteKey, 0x80000); // 512KB Observed Behavior: Direct I/O vs. Buffered I/O We've observed that the I/O behavior differs drastically depending on whether it goes through the system file cache: 1. Direct I/O (Bypassing System Cache) -> 100% Successful When we use fio with the direct=1 flag, our read/write and data verification tests pass perfectly for all file sizes, including 20GB+. 2. Buffered I/O (Using System Cache) -> 100% Failure at >128MB Whether we use the standard cp command or fio with the direct=1 option removed to simulate buffered I/O, we observe the exact same, clear failure threshold: Test Results: File sizes ≤ 128MB: Success. Data checksums match perfectly. File sizes ≥ 256MB: Failure. Checksums do not match, and the destination file is corrupted. Evidence of failure reproduced with fio (buffered_integrity_test.fio, with direct=1 removed): fio --size=128M buffered_integrity_test.fio -> Test Succeeded (err=0). fio --size=256M buffered_integrity_test.fio -> Test Failed (err=92), reporting the following error, which proves a data mismatch during the verification phase: verify: bad header ... at file ... offset 1048576, length 1048576 fio: ... error=Illegal byte sequence Our Analysis and Hypothesis The phenomenon of "Direct I/O succeeding while Buffered I/O fails" suggests the problem may be related to the cache synchronization mechanism at the end of the I/O process: Our UserProcessParallelTask_Impl function correctly handles READ and WRITE commands. When cp or fio (buffered) runs, the WRITE commands are successfully written to the LSI 3108 controller's onboard DRAM cache, and success is reported up the stack. At the end of the operation, to ensure data is flushed to disk, the macOS file system issues an fsync, which is ultimately translated into a SYNCHRONIZE CACHE SCSI command (Opcode 0x35 or 0x91) and sent to our UserProcessParallelTask_Impl. We hypothesize that our code may not be correctly identifying or handling this SYNCHRONIZE CACHE opcode. It might be reporting "success" up the stack without actually commanding the hardware to flush its cache to the physical disk. The OS receives this "success" status and assumes the operation is safely complete. In reality, however, the last batch of data remains only in the controller's volatile DRAM cache and is eventually lost. This results in an incomplete or incorrect file tail, and while the file size may be correct, the data checksum will inevitably fail. Summary Our DEXT driver performs correctly when handling Direct I/O but consistently fails with data corruption when handling Buffered I/O for files larger than 128MB. We can reliably reproduce this issue using fio with the direct=1 option removed. The root cause is very likely the improper handling of the SYNCHRONIZE CACHE command within our UserProcessParallelTask. P.S. This issue did not exist in the original KEXT version of the driver. We would appreciate any advice or guidance on this issue. Thank you.
13
0
1.8k
Dec ’25
Archive Fails: Conflict with Existing KEXT Developer ID Certificate
Hi everyone, We're trying to prepare a DriverKit App for a client test, and we've run into an unavoidable signing conflict that seems to be caused by the Xcode Archive process itself. Background & Environment: Environment: macOS 15.6.1, Xcode 16.4 Our project consists of a main App Target and a DEXT Target. Both the Debug and Release configurations for both targets are set to Xcode's default: Automatically manage signing. Our developer account holds a valid, active Developer ID Application (With Kext) certificate, which we use for signing our legacy KEXT. The Action That Triggers Failure: From this clean state, we execute Product -> Archive. The Archive process fails during the signing validation phase and presents the following three errors, completely halting the process: There is a problem with the request entity - You already have a current Developer ID Application Managed (With Kext) certificate... No profiles for 'com.company.Acxxx.driver' were found... No profiles for 'com.company.Acxxx.app' were found... This error seems to indicate that the Xcode Archive process: Ignores the project's Release configuration (even the default 'Auto' setting). Attempts to automatically create a new, standard Developer ID certificate for us. This action conflicts with the existing (With Kext) certificate in our account, causing the entire Archive process to fail. The "Failed Experiment" to Resolve This: To work around this automation conflict, we tried the solution: configuring a fully manual signing process for the Release configuration to explicitly tell Xcode to use our existing KEXT certificate. Our Steps: We disabled automatic signing for both the App and DEXT targets for the Release configuration and manually assigned the Developer ID Provisioning Profiles created for our Developer ID (With Kext) certificate. The New Problem: After doing this, the Signing Certificate field for the DEXT Target's Signing & Capabilities interface now shows None, accompanied by the misleading warning about needing a DriverKit development profile. The Outcome: This None issue now prevents us from even starting the Archive process, as the project fails to build due to the incorrect signing configuration. We've tried every debugging step — including rebuilding profiles, validating the keychain, and clearing caches — but nothing resolves this None issue. Our Dilemma: State A (Fully Automatic Signing): The Archive process fails due to the KEXT certificate conflict. State B (Manual Release Signing): The project fails to build due to the Signing Certificate: None issue, preventing us from initiating an Archive. For a development team holding a KEXT Developer ID certificate, how should an Xcode project be configured when migrating to DriverKit, so that the Archive process: Does not trigger the flawed automation logic that attempts to create a new certificate? And, does not fall into the Signing Certificate: None configuration trap? Related Forum Threads We've Studied: https://developer.apple.com/forums/thread/781932 https://developer.apple.com/forums/thread/751490 https://developer.apple.com/forums/thread/767152 https://developer.apple.com/forums/thread/721563 Best Reagrds, Charles
2
0
1.5k
Dec ’25
Architectural Performance Difference in External Storage I/O Between Intel and Apple Silicon Macs
Hi everyone, We are in the process of migrating a legacy KEXT for our external multi-disk RAID enclosure to the modern DriverKit framework. During the performance validation of our KEXT, we observed a large and consistent maximum throughput difference between Intel-based Macs and Apple Silicon-based Macs. We would like to share our findings and hope to discuss with others in the community to see if you have had similar experiences that could confirm or correct our understanding. The Observation: A Consistent Performance Gap When using the exact same external RAID hardware (an 8-HDD RAID 5 array), driven by our mature KEXT, we see the following results in high-throughput benchmarks (AJA System Test, large sequential writes): On a 2020 Intel-based Mac: We consistently achieve a throughput of ~2500 MB/s. On modern M-series Macs (from M1 to M4): The throughput is consistently capped at ~1500 MB/s. This performance difference of nearly 40% is significant and is present across the entire Apple Silicon product line. Our Hypothesis: A Shift in Architectural Design Philosophy Since the KEXT and external hardware are identical in both tests, we believe this performance difference is not a bug but a fundamental platform architecture distinction. Our hypothesis is as follows: 1. The Intel Mac Era ("Dedicated Throughput") The Intel-based Macs we tested use a dedicated, discrete Intel Thunderbolt controller chip. This chip has its own dedicated PCIe lanes and resources, and its design appears to be singularly focused on maximizing raw, sustained data throughput for external peripherals. 2. The Apple Silicon Era ("Integrated Efficiency") In contrast, M-series Macs use a deeply integrated I/O controller inside the SoC. This controller must share resources, such as the total unified memory bandwidth and the chip's overall power budget, with all other functional units (CPU, GPU, etc.). We speculate that the design priority for this integrated I/O controller has shifted from "maximizing single-task raw throughput" to "maximizing overall system efficiency, multi-task responsiveness, and low latency." As a result, in a pure, single-task storage benchmark, its performance ceiling may be lower than that of the older, dedicated-chip architecture. Our Question to the Community: Is our understanding correct? Have other developers of high-performance storage drivers or peripherals also observed a similar performance ceiling for external storage on Apple Silicon Macs, when compared to high-end Intel Macs? We believe that understanding this as a deliberate architectural trade-off is crucial for setting realistic performance targets for our DEXT. Our current goal has been adjusted to have our DEXT match the KEXT's ~1500 MB/s on the M-series platform. Any insights, confirmations, or corrections from the community or Apple engineers would be greatly appreciated. Thank you very much! Charles
1
0
1.3k
Nov ’25
DriverKit DEXT Logs Only Once, Subsequent Logs Are Lost Despite Normal Functionality
Hello everyone, I've encountered a very strange and persistent logging issue with my DriverKit DEXT and would appreciate any insights from the community. [Problem Summary] My DriverKit DEXT, along with its companion Swift app, is functionally working perfectly. I can repeatedly call methods in the DEXT from the app (e.g., a Ping-Pong test and a StaticProcessInbandTask call) and receive the correct response every time. However, the os_log messages within my IOUserClient subclass are only successfully recorded for the very first set of interactions. After that, all subsequent logs are completely missing. What's even stranger is that all successfully recorded logs are attributed to the kernel: process, even for purely user-space methods like ExternalMethod. [Development Environment] macOS: 15.7.1 Xcode: 16.4 Hardware: MacBook Pro M1 DEXT Logging Macro (Log.h): #include <os/log.h> #define Log(fmt, ...) \ do { \ os_log(OS_LOG_DEFAULT, "[%{public}s] " fmt, __FUNCTION__, ##__VA_ARGS__); \ } while (0) [Steps to Reproduce & Observed Behavior] The DEXT is successfully loaded via the companion app. I click the "Ping-Pong" button, then the "Process InBand" button in the app. The app's UI log correctly shows that the request was sent and a successful response was received from the DEXT. I repeat step 2 multiple times. Each interaction works flawlessly from the app's perspective. I then use the log show command to export the logs from this period, for example: log show --last 5m | grep "com.accusys.Acxxx.driver" > dext_logs.txt Observed Result (Log Content): In the dext_logs.txt file, I can only see the logs from the very first Ping-Pong and the very first Process InBand call. All subsequent, successful operations leave no trace in the logs. kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // { --- kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // --- } kernel: (com.accusys.Acxxx.driver.dext) [StaticPingPong] // { --- kernel: (com.accusys.Acxxx.driver.dext) [StaticPingPong] // --- } kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // { --- kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // --- } kernel: (com.accusys.Acxxx.driver.dext) [StaticProcessInbandTask] // { --- kernel: (com.accusys.Acxxx.driver.dext) [StaticProcessInbandTask] // --- } <--- END OF FILE (No new logs appear after this point) ---> [Core Questions] Why are logs in IOUserClient subclass only recorded once? Given the DEXT is clearly still running and processing requests, why would os_log calls only succeed in writing to the system log database on the first interaction? Why are all logs attributed to the kernel? Why would logs from 100% user-space code like ExternalMethod and StaticPingPong be attributed to the kernel process? [Solutions Attempted That Did Not Work] I have verified with ps aux that the DEXT process (com.accusys.Acxxx.driver) is running continuously in the background and has not crashed. Attempted to force-restart the logging service with sudo killall logd, but the issue persists. Performed the most thorough reset possible using systemextensionsctl reset followed by a full reboot, then reinstalled the DEXT. The issue remains exactly the same. Thank you for any possible help or suggestions Best, Charles
3
0
570
Oct ’25
Implementing Hardware Interrupt Handling with InterruptOccurred in DriverKit
Hello everyone, I’m working on implementing hardware interrupt handling in DriverKit and came across the InterruptOccurred method in IOInterruptDispatchSource. I noticed that its declaration ends with a TYPE macro: virtual void InterruptOccurred(OSAction* action, uint64_t count, uint64_t time) TYPE(IOInterruptDispatchSource::InterruptOccurred); This structure seems similar to how Timer Events are set up, where an event is linked to a callback and triggered by a timer. I’m attempting to use a similar approach, but for hardware-triggered interrupts rather than timer events. I’m currently in the trial-and-error phase of the implementation, but if anyone has a working example or reference on how to properly implement and register InterruptOccurred, it would be greatly appreciated! Best regards, Charles
3
0
1.2k
May ’25
macOS Hang After Implementing UserMapHBAData()
Hi everyone, I was following the Video Modernize PCI and SCSI drivers with DriverKit and the Document to implement UserMapHBAData(), and here’s my current implementation: // kern_return_t DRV_MAIN_CLASS_NAME::UserMapHBAData_Impl(uint32_t *uniqueTaskID) kern_return_t IMPL(DRV_MAIN_CLASS_NAME, UserMapHBAData) { Log("UserMapHBAData() - Start"); // Define the vm_page_size explicitly const uint32_t vm_page_size = 4096; kern_return_t ret; IOBufferMemoryDescriptor *buffer = nullptr; IOMemoryMap *memMap = nullptr; void *taskData = nullptr; // Create a buffer for HBA-specific task data ret = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionOutIn, ivars->fTaskDataSize, vm_page_size, &buffer); __Require((kIOReturnSuccess == ret), Exit); // Map memory to the driver extension's memory space ret = buffer->CreateMapping(0, 0, 0, 0, 0, &memMap); __Require((kIOReturnSuccess == ret), Exit); // Retrieve mapped memory address taskData = reinterpret_cast<void *>(memMap->GetAddress()); __Require(taskData, Exit); // WARNING: Potential leak of an object stored into 'buffer' // WARNING: Potential leak of an object stored into 'memMap' // Assign a unique task ID ivars->fTaskID++; // ERROR: No member named 'fTaskID' in 'DriverKitAcxxx_IVars' ivars->fTaskArray[ivars->fTaskID] = taskData; *uniqueTaskID = ivars->fTaskID; Log("UserMapHBAData() - End"); return kIOReturnSuccess; Exit: // Cleanup in case of failure if (memMap) { memMap->free(); // Correct method for releasing memory maps } if (buffer) { buffer->free(); // Correct method for releasing buffer memory } LogErr("ret = 0x%0x", ret); Log("UserMapHBAData() - End"); return ret; } For reference, in KEXT, memory allocation is typically done using: IOBufferMemoryDescriptor *buffer = IOBufferMemoryDescriptor::inTaskWithOptions( kernel_task, // Task in which memory is allocated kIODirectionOutIn, // Direction (read/write) 1024, // Size of the buffer in bytes 4); // Alignment requirements However, after installing the dext, macOS hangs, and I have to do a hardware reset. After rebooting, the sysctl list output shows: % sectl list 1 extension(s) --- com.apple.system_extension.driver_extension enabled active teamID bundleID (version) name [state] * - com.accusys.DriverKitAcxxx (5.0/11) com.accusys.DriverKitAcxxx [activated waiting for user] Questions: What could be causing macOS to halt? How should I approach debugging and resolving this issue? Looking forward to your insights, any suggestions would be greatly appreciated! Best regards, Charles
7
0
1.5k
May ’25
How to setup DriverKit Timer Event with OSAction Callback Binding
Hello Everyone, I'm encountering an issue while setting up a timer event in DriverKit and would appreciate any guidance. Here's my current implementation: void DRV_MAIN_CLASS_NAME::SetupEventTimer() { // 1. Create dispatch queue kern_return_t ret = IODispatchQueue::Create("TimerQueue", 0, 0, &ivars->dispatchQueue); if (ret != kIOReturnSuccess) { LogErr("Failed to create dispatch queue: 0x%x", ret); return; } // 2. Create timer source ret = IOTimerDispatchSource::Create(ivars->dispatchQueue, &ivars->dispatchSource); if (ret != kIOReturnSuccess) { LogErr("Failed to create timer: 0x%x", ret); OSSafeReleaseNULL(ivars->dispatchQueue); return; } /*! * @brief Create an instance of OSAction. * @discussion Methods to allocate an OSAction instance are generated for each method defined in a class with * a TYPE attribute, so there should not be any need to directly call OSAction::Create(). * @param target OSObject to receive the callback. This object will be retained until the OSAction is * canceled or freed. * @param targetmsgid Generated message ID for the target method. * @param msgid Generated message ID for the method invoked by the receiver of the OSAction * to generate the callback. * @param referenceSize Size of additional state structure available to the creator of the OSAction * with GetReference. * @param action Created OSAction with +1 retain count to be released by the caller. * @return kIOReturnSuccess on success. See IOReturn.h for error codes. */ // 3: Create an OSAction for the TimerOccurred method // THIS IS WHERE I NEED HELP OSAction* timerAction = nullptr; ret = OSAction::Create(this, 0, 0, 0, &timerAction); if (ret != kIOReturnSuccess) { LogErr("Failed to create OSAction: 0x%x", ret); goto cleanup; } // 4. Set handler ret = ivars->dispatchSource->SetHandler(timerAction); if (ret != kIOReturnSuccess) { LogErr("Failed to set handler: 0x%x", ret); goto cleanup; } // 5. Schedule timer (1 second) uint64_t deadline = mach_absolute_time() + NSEC_PER_SEC; ivars->dispatchSource->WakeAtTime(0, deadline, 0); cleanup: if (ret != kIOReturnSuccess) { OSSafeReleaseNULL(timerAction); OSSafeReleaseNULL(ivars->dispatchSource); OSSafeReleaseNULL(ivars->dispatchQueue); } } Problem: The code runs but the OSAction callback binding seems incorrect (Step 3). According to the OSAction documentation, I need to use the TYPE macro to properly bind the callback method. But I try to use TYPE(DRV_MAIN_CLASS_NAME::TimerOccurred) kern_return_t TimerOccurred() LOCALONLY; TYPE(TimerOccurred) kern_return_t TimerOccurred() LOCALONLY; kern_return_t TimerOccurred() TYPE(DRV_MAIN_CLASS_NAME::TimerOccurred) LOCALONLY; All results in Out-of-line definition of 'TimerOccurred' does not match any declaration in 'DRV_MAIN_CLASS_NAME' Questions: What is the correct way to declare a timer callback method using TYPE? How to get the values targetmsgid & msgid generated by Xcode? Any help would be greatly appreciated! Best Regards, Charles
6
0
1.2k
Apr ’25
Issue Writing to BAR1 After BAR0 is Unavailable
Hello Everyone, I encountered an issue with PCI memory access in DriverKit. In my case, BAR0 is not available, but BAR1 is ready for use. Here’s the log output: !!! ERROR : Failed to get BAR0 info (error: 0xe00002f0). !!! BAR1 - MemoryIndex: 0x00000000, Size: 0x00040000, Type: 0 Issue Description When I initially wrote to BAR0 using memoryIndex = 0, it worked successfully: AME_Address_Write_32(pAMEData, pAMEData->memoryIndex, AME_HOST_INT_MASK_REGISTER, 0x0F); However, I mistakenly forgot to update memoryIndex to 1 for BAR1. Surprisingly, the write operation still succeeded. When I fixed memoryIndex = 1 for BAR1, the write operation no longer had any effect. There was no error, but the expected behavior did not occur. Relevant API (From IOPCIDevice.iig) /*! /*! * @brief Writes a 32-bit value to the PCI device's aperture at a given memory index. * @discussion This method writes a 32-bit register on the device and returns its value. * @param memoryIndex An index into the array of ranges assigned to the device. * @param offset An offset into the device's memory specified by the index. * @param data A 32-bit value to be written in host byte order. */ void MemoryWrite32(uint8_t memoryIndex, uint64_t offset, uint32_t data) LOCALONLY; Log Output: Writes to BAR0 (memoryIndex = 0) AME_Address_Write_32() called memoryIndex: 0, offset: 0x34, data: 0xf Wrote data 0xF to offset 52 AME_Address_Write_32() called memoryIndex: 0, offset: 0xa0, data: 0x1 Wrote data 0x1 to offset 160 AME_Address_Write_32() called memoryIndex: 0, offset: 0x20, data: 0xffffffff Wrote data 0xFFFFFFFF to offset 32 Writes to BAR1 (memoryIndex = 1) – No Response AME_Address_Write_32() called memoryIndex: 1, offset: 0x34, data: 0xf No confirmation log, no visible effect. Questions What should memoryIndex be set to for BAR1? The log shows "BAR1 - MemoryIndex: 0x00000000", but should I be using 1 instead? How can I verify if a write operation to BAR1 is successful? Is there a way to check if the memory region is actually writable? Should I use MemoryRead32() to confirm the written value? Any guidance would be greatly appreciated! Best Regards, Charles
3
1
681
Mar ’25
DEXT receives zero-filled buffer from DMA, despite firmware confirming data write
Hello everyone, I am migrating a KEXT for a SCSI PCI RAID controller (LSI 3108 RoC) to DriverKit (DEXT). While the DEXT loads successfully, I'm facing a DMA issue: an INQUIRY command results in a 0-byte disk because the data buffer received by the DEXT is all zeros, despite our firmware logs confirming that the correct data was prepared and sent. We have gathered detailed forensic evidence and would appreciate any insights from the community. Detailed Trace of a Failing INQUIRY Command: 1, DEXT Dispatches the Command: Our UserProcessParallelTask implementation correctly receives the INQUIRY task. Logs show the requested transfer size is 6 bytes, and the DEXT obtains the IOVA (0x801c0000) to pass to the hardware. DEXT Log: [UserProcessParallelTask_Impl] --- FORENSIC ANALYSIS --- [UserProcessParallelTask_Impl] fBufferIOVMAddr = 0x801c0000 [UserProcessParallelTask_Impl] fRequestedTransferCount = 6 2, Firmware Receives IOVA and Prepares Correct Data: A probe in our firmware confirms that the hardware successfully received the correct IOVA and the 6-byte length requirement. The firmware then prepares the correct 6-byte INQUIRY response in its internal staging buffer. Firmware Logs: -- [FIRMWARE PROBE: INCOMING DMA DUMP] -- Host IOVA (High:Low) = 0x00000000801c0000 DataLength in Header = 6 (0x6) --- [Firmware Outgoing Data Dump from go_inquiry] --- Source Address: 0x228BB800, Length: 6 bytes 0x0000: 00 00 05 12 1F 00 3, Hardware Reports a Successful Transfer, but Data is Lost: After the firmware initiates the DMA write to the Host IOVA, the hardware reports a successful transfer of 6 bytes back to our DEXT. DEXT Completion Log: [AME_Host_Normal_Handler_SCSI_Request] [TaskID: 200] COMPLETING... [AME_Host_Normal_Handler_SCSI_Request] Hardware Transferred = 6 bytes [AME_Host_Normal_Handler_SCSI_Request] - ReplyStatus = SUCCESS (0x0) [AME_Host_Normal_Handler_SCSI_Request] - SCSIStatus = SUCCESS (0x0) The Core Contradiction: Despite the firmware preparing the correct data and the hardware reporting a successful DMA transfer, the fDataBuffer in our DEXT remains filled with zeros. The 6 bytes of data are lost somewhere between the PCIe bus and host memory. This "data-in-firmware, zeros-in-DEXT" phenomenon leads us to believe the issue lies in memory address translation or a system security policy, as our legacy KEXT works perfectly on the same hardware. Compared to a KEXT, are there any known, stricter IOMMU/security policies for a DEXT that could cause this kind of "silent write failure" (even with a correct IOVA)? Alternatively, what is the correct and complete expected workflow in DriverKit for preparing an IOMemoryDescriptor* fDataBuffer (received in UserProcessParallelTask) for a PCI hardware device to use as a DMA write target? Any official documentation, examples, or advice on the IOMemoryDescriptor to PCI Bus Address workflow would be immensely helpful. Thank you. Charles
Replies
5
Boosts
0
Views
1.8k
Activity
Aug ’26
[DEXT Migration Issue] IOUserSCSIParallelInterfaceController fails to handle low-level I/O from `diskutil`
Hello everyone, We are migrating our KEXT for a Thunderbolt storage device to a DEXT based on IOUserSCSIParallelInterfaceController. We've run into a fundamental issue where the driver's behavior splits based on the I/O source: high-level I/O from the file system (e.g., Finder, cp) is mostly functional (with a minor ls -al sorting issue for Traditional Chinese filenames), while low-level I/O directly to the block device (e.g., diskutil) fails or acts unreliably. Basic read/write with dd appears to be mostly functional. We suspect that our DEXT is failing to correctly register its full device "personality" with the I/O Kit framework, unlike its KEXT counterpart. As a result, low-level I/O requests with special attributes (like cache synchronization) sent by diskutil are not being handled correctly by the IOUserSCSIParallelInterfaceController framework of our DEXT. Actions Performed & Relevant Logs 1. Discrepancy: diskutil info Shows Different Device Identities for DEXT vs. KEXT For the exact same hardware, the KEXT and DEXT are identified by the system as two different protocols. KEXT Environment: Device Identifier: disk5 Protocol: Fibre Channel Interface ... Disk Size: 66.0 TB Device Block Size: 512 Bytes DEXT Environment: Device Identifier: disk5 Protocol: SCSI SCSI Domain ID: 2 SCSI Target ID: 0 ... Disk Size: 66.0 TB Device Block Size: 512 Bytes 2. Divergent I/O Behavior: Partial Success with Finder/cp vs. Failure with diskutil High-Level I/O (Partially Successful): In the DEXT environment, if we operate on an existing volume (e.g., /Volumes/MyVolume), file copy operations using Finder or cp succeed. Furthermore, the logs we've placed in our single I/O entry point, UserProcessParallelTask_Impl, are triggered. Side Effect: However, running ls -al on such a volume shows an incorrect sorting order for files with Traditional Chinese names (they appear before . and ..). Low-Level I/O (Contradictory Behavior): In the DEXT environment, when we operate directly on the raw block device (/dev/disk5): diskutil partitionDisk ... -> Fails 100% of the time with the error: Error: -69825: Wiping volume data to prevent future accidental probing failed. dd command -> Basic read/write operations appear to work correctly (a write can be immediately followed by a read within the same DEXT session, and the data is correct). 3. Evidence of Cache Synchronization Failure (Non-deterministic Behavior) The success of the dd command is not deterministic. Cross-environment tests prove that its write operations are unreliable: First Test: In the DEXT environment, write a file with random data to /dev/disk5 using dd. Reboot into the KEXT environment. Read the data back from /dev/disk5 using dd. The result is a file filled with all zeros. Conclusion: The write operation only went to the hardware cache, and the data was lost upon reboot. Second Test: In the DEXT environment, write the same random file to /dev/disk5 using dd. Key Variable: Immediately after, still within the DEXT environment, read the data back once for verification. The content is correct! Reboot into the KEXT environment. Read the data back from /dev/disk5. This time, the content is correct! Conclusion: The additional read operation in the second test unintentionally triggered a hardware cache flush. This proves that the dd (in our DEXT) write operation by itself does not guarantee synchronization, making its behavior unreliable. Our Problem Based on the observations above, we have the conclusion: High-Level Path (triggered by Finder/cp): When an I/O request originates from the high-level file system, the framework seems to enter a fully-featured mode. In this mode, all SCSI commands, including READ/WRITE, INQUIRY, and SYNCHRONIZE CACHE, are correctly packaged and dispatched to our UserProcessParallelTask_Impl entry point. Therefore, Finder operations are mostly functional. Low-Level Path (triggered by dd/diskutil): When an I/O request originates from the low-level raw block device layer: The most basic READ/WRITE commands can be dispatched (which is why dd appears to work). However, critical management commands, such as INQUIRY and SYNCHRONIZE CACHE, are not being correctly dispatched or handled. This leads to the incorrect device identification in diskutil info and the failure of diskutil partitionDisk due to its inability to confirm cache synchronization. We would greatly appreciate any guidance, suggestions, or insights on how to resolve this discrepancy. Specifically, what is the recommended approach within DriverKit to ensure that a DEXT based on IOUserSCSIParallelInterfaceController can properly declare its capabilities and handle both high-level and low-level I/O requests uniformly? Thank you. Charles
Replies
12
Boosts
0
Views
2.5k
Activity
Jul ’26
System Panic with IOUserSCSIParallelInterfaceController during Dispatch Queue Configuration
Hello everyone, We are in the process of migrating a high-performance storage KEXT to DriverKit. During our initial validation phase, we noticed a performance gap between the DEXT and the KEXT, which prompted us to try and optimize our I/O handling process. Background and Motivation: Our test hardware is a RAID 0 array of two HDDs. According to AJA System Test, our legacy KEXT achieves a write speed of about 645 MB/s on this hardware, whereas the new DEXT reaches about 565 MB/s. We suspect the primary reason for this performance gap might be that the DEXT, by default, uses a serial work-loop to submit I/O commands, which fails to fully leverage the parallelism of the hardware array. Therefore, to eliminate this bottleneck and improve performance, we configured a dedicated parallel dispatch queue (MyParallelIOQueue) for the UserProcessParallelTask method. However, during our implementation attempt, we encountered a critical issue that caused a system-wide crash. The Operation Causing the Panic: We configured MyParallelIOQueue using the following combination of methods: In the .iig file: We appended the QUEUENAME(MyParallelIOQueue) macro after the override keyword of the UserProcessParallelTask method declaration. In the .cpp file: We manually created a queue with the same name by calling the IODispatchQueue::Create() function within our UserInitializeController method. The Result: This results in a macOS kernel panic during the DEXT loading process, forcing the user to perform a hard reboot. After the reboot, checking with the systemextensionsctl list command reveals the DEXT's status as [activated waiting for user], which indicates that it encountered an unrecoverable, fatal error during its initialization. Key Code Snippets to Reproduce the Panic: In .iig file - this was our exact implementation: class DRV_MAIN_CLASS_NAME: public IOUserSCSIParallelInterfaceController { public: virtual kern_return_t UserProcessParallelTask(...) override QUEUENAME(MyParallelIOQueue); }; In .h file: struct DRV_MAIN_CLASS_NAME_IVars { // ... IODispatchQueue* MyParallelIOQueue; }; In UserInitializeController implementation: kern_return_t IMPL(DRV_MAIN_CLASS_NAME, UserInitializeController) { // ... // We also included code to manually create the queue. kern_return_t ret = IODispatchQueue::Create("MyParallelIOQueue", kIODispatchQueueReentrant, 0, &ivars->MyParallelIOQueue); if (ret != kIOReturnSuccess) { // ... error handling ... } // ... return kIOReturnSuccess; } Our Question: What is the officially recommended and most stable method for configuring UserProcessParallelTask_Impl() to use a parallel I/O queue? Clarifying this is crucial for all developers pursuing high-performance storage solutions with DriverKit. Any explanation or guidance would be greatly appreciated. Best Regards, Charles
Replies
28
Boosts
0
Views
5.3k
Activity
Jun ’26
Optimizing SCSI HBA Constraints and Alignment for DriverKit on Apple Silicon
Hi Kevin, I'm starting this new thread to focus on alignment optimization and recalibrating our HBA constraints. Following up on your suggestion about UserReportHBAConstraints and alignment optimization, here are our current DEXT settings: Via UserReportHBAConstraints(): kIOMaximumSegmentCountRead/WriteKey: 129 kIOMaximumSegmentByteCountRead/WriteKey: 65,536 (64 KB) kIOMinimumSegmentAlignmentByteCountKey: 4 bytes kIOMaximumSegmentAddressableBitCountKey: 32 kIOMinimumHBADataAlignmentMaskKey: 0 Via SetProperties() (additional injection): kIOMaximumByteCountRead/WriteKey: 524,288 (512 KB) kIOMaximumBlockCountRead/WriteKey: 1,024 We inherited the segment count (129) and max I/O length (512 KB) from our legacy KEXT, which were originally calculated based on a 4 KB segment size (Max I/O 512 KB / 4 KB + 1 = 129). The current alignment value of 4 was essentially a placeholder, as the legacy hardware didn't enforce strict page-level alignment. Given that our testing is on Apple Silicon, we are considering increasing kIOMinimumSegmentAlignmentByteCountKey to 16,384 (16 KB) to match the native page size. However, I have two specific questions regarding this: Stripe Size vs. Page Size: Our RAID stripe size is typically larger than 16 KB (e.g., 64 KB or 128 KB). Should we be aligning the system to the RAID stripe size for hardware efficiency, or is it more critical to stick to the 16 KB page size to optimize the IOMMU/DART mapping overhead in DriverKit? Recalibration: If we increase the alignment to 16 KB, should we also adjust the kIOMaximumSegmentByteCount to match (i.e., 16 KB), or is it better to keep it at 64 KB to allow fewer, larger segments per I/O? We suspect that the 38% gain we saw in 4 KB Random Reads might improve even further if we fix this alignment bottleneck. Looking forward to your thoughts. Best regards, Charles
Replies
4
Boosts
0
Views
1.9k
Activity
Jun ’26
Xcode Signing Fails: Provisioning Profile "doesn't match" com.apple.developer.driverkit.userclient-access entitlement
Hello everyone, I am migrating a legacy KEXT to a DriverKit (DEXT) architecture. While the DEXT itself is working correctly, I am completely blocked by a code signing issue when trying to establish the UserClient connection from our SwiftUI management app. Project Goal & Status: Our DEXT (com.accusys.Acxxx.driver) activates successfully (systemextensionsctl list confirms [activated enabled]). The core functionality is working (diskutil list shows the corresponding disk device node). The Core Problem: The userclient-access Signing Error To allow the app to connect to the DEXT, the com.apple.developer.driverkit.userclient-access entitlement is required in the app's .entitlements file. However, as soon as this entitlement is added, the build fails. Both automatic and manual signing fail with the same error: `Provisioning profile ... doesn't match the entitlements file's value for the ... userclient-access entitlement.` This build failure prevents the generation of an .app bundle, making it impossible to inspect the final entitlements with codesign. What We've Confirmed: The necessary capabilities (like DriverKit Communicates with Drivers) are visible and enabled for our App ID on the developer portal. The issue persists on a clean system state and on the latest macOS Sequoia 15.7.1. Our Research and Hypothesis: We have reviewed the official documentation "Diagnosing issues with entitlements" (TN3125). According to the documentation, a "doesn't match" error implies a discrepancy between the entitlements file and the provisioning profile. Given that we have tried both automatic and manual profiles (after enabling the capability online), our hypothesis is that the provisioning profile generation process on Apple's backend is not correctly including the approved userclient-access entitlement into the profile file itself. The build fails because Xcode correctly detects this discrepancy. Our Questions: Did we misunderstand a step in the process, or is the issue not with the entitlement request at all? Alternatively, are there any other modifications we can make to successfully connect our App to the DEXT and trigger NewUserClient? Thank you for any guidance.
Replies
11
Boosts
0
Views
2.0k
Activity
May ’26
Kernel Panic: Power state transition (0 -> 2) timeout during DriverKit (DEXT) load sequence (IOUserSCSIParallelInterfaceController)
Hi Everyone, We are currently migrating a mature legacy KEXT to DriverKit for our PCIe SCSI storage controller (connected via Thunderbolt 3). During the DEXT load sequence, we have observed that the system automatically triggers a power state transition from State 0 (Off) to State 2 (On). However, this process results in a Kernel Panic due to a timeout after approximately 21 seconds. We have verified that our implementation of Start_Impl, UserInitializeController_Impl, and SetPowerState_Impl executes extremely fast, with a total execution time of less than one second. Specifically, SetPowerState_Impl returns kIOReturnSuccess immediately upon being called. Furthermore, our current Info.plist does not contain any IOPowerManagement dictionary or related keys. Despite the fast execution and the absence of explicit power management declarations in the plist, the kernel power management state machine (IOServicePM) still generates a 21-second timeout, leading to the following panic: Panic Log: panic(cpu 7 caller 0xfffffe0020be8fec): MySCSIDriver::setPowerState(0xfffffe2fb1a65c00 : 0xfffffe0020bfed88, 0 -> 2) timed out after 21257 ms @IOServicePM.cpp:5609 com.example.driver.dext: ( id: com.example.driver.dext; path: /Library/SystemExtensions/[UUID]/com.example.driver.dext; state: loaded ) Note on Previous Discussion: I would like to express my gratitude to Kevin from Apple DTS for the helpful discussion regarding the implementation of BundleParallelTask on the forums. Since then, we have shifted our development focus toward completing the overall management ecosystem, delivering a comprehensive operational interface for users, and handling specific user environments and behaviors. Our current priority is ensuring system stability—specifically resolving these Thunderbolt-related power management issues (sleep/wake)—to prepare the product for upcoming testing. I remain very grateful for the guidance provided on batch task optimization and intend to resume those optimizations once this critical stability baseline is secured. Technical Guidance Needed for PM Migration In our legacy KEXT, we utilized PMinit(), registerPowerDriver(), and joinPMtree() to precisely control the timing of power management registration. In transitioning to the DriverKit SDK, we have not found clear guidance on several key points: Standardized Migration Path: What is the recommended way to implement equivalent power management initialization (formerly PMinit) within a DriverKit subclass? In DriverKit, how should we replicate the behavior of manually calling registerPowerDriver and joinPMtree to ensure the driver is only monitored once the hardware is ready? Implicit Power Registration: Why does the system enforce a setPowerState(0 -> 2) transition on a subclass of IOUserSCSIParallelInterfaceController even when no IOPowerManagement dictionary is defined in the Info.plist? Is this a default behavior of the SCSI or PCI transport framework? Thunderbolt Specifics: Are there specific power proxying requirements or configurations for PCIe devices over Thunderbolt to avoid conflicts with the default IOPCIFamily power policies? Best Regards, Charles
Replies
3
Boosts
0
Views
1.6k
Activity
Mar ’26
[DriverKit SCSI] SCSI probe stalls for Target ID > 0 with IOUserSCSIParallelInterfaceController
Hello everyone, We are migrating a KEXT storage driver to DriverKit. In our KEXT, we use a "one LUN = one Target" model and successfully create multiple targets in a loop during initialization. We are now trying to replicate this architecture in our DEXT. The issue is that only Target 0 is fully probed and mounted. For Target 1, the lifecycle silently stops after the first TEST UNIT READY command is successfully acknowledged. The macOS SCSI layer never sends any subsequent probe commands (like INQUIRY) to this target. The failure sequence for Target 1, observed from our logs (regardless of whether Target 0 is created), is as follows: AsyncCreateTargetForID(1) -> UserInitializeTargetForID(1) (Succeeds) UserProcessParallelTask(Target: 1, Opcode: TUR) (Succeeds) The DEXT correctly acknowledges the TUR command for Target 1 by returning kSCSITaskStatus_CHECK_CONDITION with UNIT ATTENTION in the Sense Data (Succeeds) <-- Breakpoint --> UserProcessParallelTask(Target: 1, Opcode: INQUIRY) (Never happens) Through log comparison, we have confirmed that the DEXT's response to the TUR command for Target 1 is identical to the successful KEXT's response. We have tried creating only Target 1 (skipping Target 0 entirely), but the behavior is exactly the same -> the probe still stalls after the TUR. We initially suspected a race condition caused by consecutive calls to AsyncCreateTargetForID(). We attempted several methods to ensure that targets are created sequentially, such as trying to build a "creation chain" using OSAction completion handlers. However, these attempts were unsuccessful due to various compilation errors and API misunderstandings. In any case, this "race condition" theory was ultimately disproven by our experiment where creating only Target 1 still resulted in failure. We would like to ask two questions: Is our inability to have a Target ID greater than 0 fully probed by macOS a bug in our own code, or could there be another reason we are unaware of? If we do indeed need a "one-after-another" creation mechanism for AsyncCreateTargetForID, what is the correct way to implement a "chained creation" using OSAction completion handlers in DriverKit? Thank you for any help or guidance. Best Regards, Charles
Replies
3
Boosts
0
Views
1.4k
Activity
Jan ’26
DEXT (IOUserSCSIParallelInterfaceController): Direct I/O Succeeds, but Buffered I/O Fails with Data Corruption on Large File Copies
Hi all, We are migrating a SCSI HBA driver from KEXT to DriverKit (DEXT), with our DEXT inheriting from IOUserSCSIParallelInterfaceController. We've encountered a data corruption issue that is reliably reproducible under specific conditions and are hoping for some assistance from the community. Hardware and Driver Configuration: Controller: LSI 3108 DEXT Configuration: We are reporting our hardware limitations to the framework via the UserReportHBAConstraints function, with the following key settings: // UserReportHBAConstraints... addConstraint(kIOMaximumSegmentAddressableBitCountKey, 0x20); // 32-bit addConstraint(kIOMaximumSegmentCountWriteKey, 129); addConstraint(kIOMaximumByteCountWriteKey, 0x80000); // 512KB Observed Behavior: Direct I/O vs. Buffered I/O We've observed that the I/O behavior differs drastically depending on whether it goes through the system file cache: 1. Direct I/O (Bypassing System Cache) -> 100% Successful When we use fio with the direct=1 flag, our read/write and data verification tests pass perfectly for all file sizes, including 20GB+. 2. Buffered I/O (Using System Cache) -> 100% Failure at >128MB Whether we use the standard cp command or fio with the direct=1 option removed to simulate buffered I/O, we observe the exact same, clear failure threshold: Test Results: File sizes ≤ 128MB: Success. Data checksums match perfectly. File sizes ≥ 256MB: Failure. Checksums do not match, and the destination file is corrupted. Evidence of failure reproduced with fio (buffered_integrity_test.fio, with direct=1 removed): fio --size=128M buffered_integrity_test.fio -> Test Succeeded (err=0). fio --size=256M buffered_integrity_test.fio -> Test Failed (err=92), reporting the following error, which proves a data mismatch during the verification phase: verify: bad header ... at file ... offset 1048576, length 1048576 fio: ... error=Illegal byte sequence Our Analysis and Hypothesis The phenomenon of "Direct I/O succeeding while Buffered I/O fails" suggests the problem may be related to the cache synchronization mechanism at the end of the I/O process: Our UserProcessParallelTask_Impl function correctly handles READ and WRITE commands. When cp or fio (buffered) runs, the WRITE commands are successfully written to the LSI 3108 controller's onboard DRAM cache, and success is reported up the stack. At the end of the operation, to ensure data is flushed to disk, the macOS file system issues an fsync, which is ultimately translated into a SYNCHRONIZE CACHE SCSI command (Opcode 0x35 or 0x91) and sent to our UserProcessParallelTask_Impl. We hypothesize that our code may not be correctly identifying or handling this SYNCHRONIZE CACHE opcode. It might be reporting "success" up the stack without actually commanding the hardware to flush its cache to the physical disk. The OS receives this "success" status and assumes the operation is safely complete. In reality, however, the last batch of data remains only in the controller's volatile DRAM cache and is eventually lost. This results in an incomplete or incorrect file tail, and while the file size may be correct, the data checksum will inevitably fail. Summary Our DEXT driver performs correctly when handling Direct I/O but consistently fails with data corruption when handling Buffered I/O for files larger than 128MB. We can reliably reproduce this issue using fio with the direct=1 option removed. The root cause is very likely the improper handling of the SYNCHRONIZE CACHE command within our UserProcessParallelTask. P.S. This issue did not exist in the original KEXT version of the driver. We would appreciate any advice or guidance on this issue. Thank you.
Replies
13
Boosts
0
Views
1.8k
Activity
Dec ’25
Archive Fails: Conflict with Existing KEXT Developer ID Certificate
Hi everyone, We're trying to prepare a DriverKit App for a client test, and we've run into an unavoidable signing conflict that seems to be caused by the Xcode Archive process itself. Background & Environment: Environment: macOS 15.6.1, Xcode 16.4 Our project consists of a main App Target and a DEXT Target. Both the Debug and Release configurations for both targets are set to Xcode's default: Automatically manage signing. Our developer account holds a valid, active Developer ID Application (With Kext) certificate, which we use for signing our legacy KEXT. The Action That Triggers Failure: From this clean state, we execute Product -> Archive. The Archive process fails during the signing validation phase and presents the following three errors, completely halting the process: There is a problem with the request entity - You already have a current Developer ID Application Managed (With Kext) certificate... No profiles for 'com.company.Acxxx.driver' were found... No profiles for 'com.company.Acxxx.app' were found... This error seems to indicate that the Xcode Archive process: Ignores the project's Release configuration (even the default 'Auto' setting). Attempts to automatically create a new, standard Developer ID certificate for us. This action conflicts with the existing (With Kext) certificate in our account, causing the entire Archive process to fail. The "Failed Experiment" to Resolve This: To work around this automation conflict, we tried the solution: configuring a fully manual signing process for the Release configuration to explicitly tell Xcode to use our existing KEXT certificate. Our Steps: We disabled automatic signing for both the App and DEXT targets for the Release configuration and manually assigned the Developer ID Provisioning Profiles created for our Developer ID (With Kext) certificate. The New Problem: After doing this, the Signing Certificate field for the DEXT Target's Signing & Capabilities interface now shows None, accompanied by the misleading warning about needing a DriverKit development profile. The Outcome: This None issue now prevents us from even starting the Archive process, as the project fails to build due to the incorrect signing configuration. We've tried every debugging step — including rebuilding profiles, validating the keychain, and clearing caches — but nothing resolves this None issue. Our Dilemma: State A (Fully Automatic Signing): The Archive process fails due to the KEXT certificate conflict. State B (Manual Release Signing): The project fails to build due to the Signing Certificate: None issue, preventing us from initiating an Archive. For a development team holding a KEXT Developer ID certificate, how should an Xcode project be configured when migrating to DriverKit, so that the Archive process: Does not trigger the flawed automation logic that attempts to create a new certificate? And, does not fall into the Signing Certificate: None configuration trap? Related Forum Threads We've Studied: https://developer.apple.com/forums/thread/781932 https://developer.apple.com/forums/thread/751490 https://developer.apple.com/forums/thread/767152 https://developer.apple.com/forums/thread/721563 Best Reagrds, Charles
Replies
2
Boosts
0
Views
1.5k
Activity
Dec ’25
Architectural Performance Difference in External Storage I/O Between Intel and Apple Silicon Macs
Hi everyone, We are in the process of migrating a legacy KEXT for our external multi-disk RAID enclosure to the modern DriverKit framework. During the performance validation of our KEXT, we observed a large and consistent maximum throughput difference between Intel-based Macs and Apple Silicon-based Macs. We would like to share our findings and hope to discuss with others in the community to see if you have had similar experiences that could confirm or correct our understanding. The Observation: A Consistent Performance Gap When using the exact same external RAID hardware (an 8-HDD RAID 5 array), driven by our mature KEXT, we see the following results in high-throughput benchmarks (AJA System Test, large sequential writes): On a 2020 Intel-based Mac: We consistently achieve a throughput of ~2500 MB/s. On modern M-series Macs (from M1 to M4): The throughput is consistently capped at ~1500 MB/s. This performance difference of nearly 40% is significant and is present across the entire Apple Silicon product line. Our Hypothesis: A Shift in Architectural Design Philosophy Since the KEXT and external hardware are identical in both tests, we believe this performance difference is not a bug but a fundamental platform architecture distinction. Our hypothesis is as follows: 1. The Intel Mac Era ("Dedicated Throughput") The Intel-based Macs we tested use a dedicated, discrete Intel Thunderbolt controller chip. This chip has its own dedicated PCIe lanes and resources, and its design appears to be singularly focused on maximizing raw, sustained data throughput for external peripherals. 2. The Apple Silicon Era ("Integrated Efficiency") In contrast, M-series Macs use a deeply integrated I/O controller inside the SoC. This controller must share resources, such as the total unified memory bandwidth and the chip's overall power budget, with all other functional units (CPU, GPU, etc.). We speculate that the design priority for this integrated I/O controller has shifted from "maximizing single-task raw throughput" to "maximizing overall system efficiency, multi-task responsiveness, and low latency." As a result, in a pure, single-task storage benchmark, its performance ceiling may be lower than that of the older, dedicated-chip architecture. Our Question to the Community: Is our understanding correct? Have other developers of high-performance storage drivers or peripherals also observed a similar performance ceiling for external storage on Apple Silicon Macs, when compared to high-end Intel Macs? We believe that understanding this as a deliberate architectural trade-off is crucial for setting realistic performance targets for our DEXT. Our current goal has been adjusted to have our DEXT match the KEXT's ~1500 MB/s on the M-series platform. Any insights, confirmations, or corrections from the community or Apple engineers would be greatly appreciated. Thank you very much! Charles
Replies
1
Boosts
0
Views
1.3k
Activity
Nov ’25
DriverKit DEXT Logs Only Once, Subsequent Logs Are Lost Despite Normal Functionality
Hello everyone, I've encountered a very strange and persistent logging issue with my DriverKit DEXT and would appreciate any insights from the community. [Problem Summary] My DriverKit DEXT, along with its companion Swift app, is functionally working perfectly. I can repeatedly call methods in the DEXT from the app (e.g., a Ping-Pong test and a StaticProcessInbandTask call) and receive the correct response every time. However, the os_log messages within my IOUserClient subclass are only successfully recorded for the very first set of interactions. After that, all subsequent logs are completely missing. What's even stranger is that all successfully recorded logs are attributed to the kernel: process, even for purely user-space methods like ExternalMethod. [Development Environment] macOS: 15.7.1 Xcode: 16.4 Hardware: MacBook Pro M1 DEXT Logging Macro (Log.h): #include <os/log.h> #define Log(fmt, ...) \ do { \ os_log(OS_LOG_DEFAULT, "[%{public}s] " fmt, __FUNCTION__, ##__VA_ARGS__); \ } while (0) [Steps to Reproduce & Observed Behavior] The DEXT is successfully loaded via the companion app. I click the "Ping-Pong" button, then the "Process InBand" button in the app. The app's UI log correctly shows that the request was sent and a successful response was received from the DEXT. I repeat step 2 multiple times. Each interaction works flawlessly from the app's perspective. I then use the log show command to export the logs from this period, for example: log show --last 5m | grep "com.accusys.Acxxx.driver" > dext_logs.txt Observed Result (Log Content): In the dext_logs.txt file, I can only see the logs from the very first Ping-Pong and the very first Process InBand call. All subsequent, successful operations leave no trace in the logs. kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // { --- kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // --- } kernel: (com.accusys.Acxxx.driver.dext) [StaticPingPong] // { --- kernel: (com.accusys.Acxxx.driver.dext) [StaticPingPong] // --- } kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // { --- kernel: (com.accusys.Acxxx.driver.dext) [ExternalMethod] // --- } kernel: (com.accusys.Acxxx.driver.dext) [StaticProcessInbandTask] // { --- kernel: (com.accusys.Acxxx.driver.dext) [StaticProcessInbandTask] // --- } <--- END OF FILE (No new logs appear after this point) ---> [Core Questions] Why are logs in IOUserClient subclass only recorded once? Given the DEXT is clearly still running and processing requests, why would os_log calls only succeed in writing to the system log database on the first interaction? Why are all logs attributed to the kernel? Why would logs from 100% user-space code like ExternalMethod and StaticPingPong be attributed to the kernel process? [Solutions Attempted That Did Not Work] I have verified with ps aux that the DEXT process (com.accusys.Acxxx.driver) is running continuously in the background and has not crashed. Attempted to force-restart the logging service with sudo killall logd, but the issue persists. Performed the most thorough reset possible using systemextensionsctl reset followed by a full reboot, then reinstalled the DEXT. The issue remains exactly the same. Thank you for any possible help or suggestions Best, Charles
Replies
3
Boosts
0
Views
570
Activity
Oct ’25
Implementing Hardware Interrupt Handling with InterruptOccurred in DriverKit
Hello everyone, I’m working on implementing hardware interrupt handling in DriverKit and came across the InterruptOccurred method in IOInterruptDispatchSource. I noticed that its declaration ends with a TYPE macro: virtual void InterruptOccurred(OSAction* action, uint64_t count, uint64_t time) TYPE(IOInterruptDispatchSource::InterruptOccurred); This structure seems similar to how Timer Events are set up, where an event is linked to a callback and triggered by a timer. I’m attempting to use a similar approach, but for hardware-triggered interrupts rather than timer events. I’m currently in the trial-and-error phase of the implementation, but if anyone has a working example or reference on how to properly implement and register InterruptOccurred, it would be greatly appreciated! Best regards, Charles
Replies
3
Boosts
0
Views
1.2k
Activity
May ’25
macOS Hang After Implementing UserMapHBAData()
Hi everyone, I was following the Video Modernize PCI and SCSI drivers with DriverKit and the Document to implement UserMapHBAData(), and here’s my current implementation: // kern_return_t DRV_MAIN_CLASS_NAME::UserMapHBAData_Impl(uint32_t *uniqueTaskID) kern_return_t IMPL(DRV_MAIN_CLASS_NAME, UserMapHBAData) { Log("UserMapHBAData() - Start"); // Define the vm_page_size explicitly const uint32_t vm_page_size = 4096; kern_return_t ret; IOBufferMemoryDescriptor *buffer = nullptr; IOMemoryMap *memMap = nullptr; void *taskData = nullptr; // Create a buffer for HBA-specific task data ret = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionOutIn, ivars->fTaskDataSize, vm_page_size, &buffer); __Require((kIOReturnSuccess == ret), Exit); // Map memory to the driver extension's memory space ret = buffer->CreateMapping(0, 0, 0, 0, 0, &memMap); __Require((kIOReturnSuccess == ret), Exit); // Retrieve mapped memory address taskData = reinterpret_cast<void *>(memMap->GetAddress()); __Require(taskData, Exit); // WARNING: Potential leak of an object stored into 'buffer' // WARNING: Potential leak of an object stored into 'memMap' // Assign a unique task ID ivars->fTaskID++; // ERROR: No member named 'fTaskID' in 'DriverKitAcxxx_IVars' ivars->fTaskArray[ivars->fTaskID] = taskData; *uniqueTaskID = ivars->fTaskID; Log("UserMapHBAData() - End"); return kIOReturnSuccess; Exit: // Cleanup in case of failure if (memMap) { memMap->free(); // Correct method for releasing memory maps } if (buffer) { buffer->free(); // Correct method for releasing buffer memory } LogErr("ret = 0x%0x", ret); Log("UserMapHBAData() - End"); return ret; } For reference, in KEXT, memory allocation is typically done using: IOBufferMemoryDescriptor *buffer = IOBufferMemoryDescriptor::inTaskWithOptions( kernel_task, // Task in which memory is allocated kIODirectionOutIn, // Direction (read/write) 1024, // Size of the buffer in bytes 4); // Alignment requirements However, after installing the dext, macOS hangs, and I have to do a hardware reset. After rebooting, the sysctl list output shows: % sectl list 1 extension(s) --- com.apple.system_extension.driver_extension enabled active teamID bundleID (version) name [state] * - com.accusys.DriverKitAcxxx (5.0/11) com.accusys.DriverKitAcxxx [activated waiting for user] Questions: What could be causing macOS to halt? How should I approach debugging and resolving this issue? Looking forward to your insights, any suggestions would be greatly appreciated! Best regards, Charles
Replies
7
Boosts
0
Views
1.5k
Activity
May ’25
How to setup DriverKit Timer Event with OSAction Callback Binding
Hello Everyone, I'm encountering an issue while setting up a timer event in DriverKit and would appreciate any guidance. Here's my current implementation: void DRV_MAIN_CLASS_NAME::SetupEventTimer() { // 1. Create dispatch queue kern_return_t ret = IODispatchQueue::Create("TimerQueue", 0, 0, &ivars->dispatchQueue); if (ret != kIOReturnSuccess) { LogErr("Failed to create dispatch queue: 0x%x", ret); return; } // 2. Create timer source ret = IOTimerDispatchSource::Create(ivars->dispatchQueue, &ivars->dispatchSource); if (ret != kIOReturnSuccess) { LogErr("Failed to create timer: 0x%x", ret); OSSafeReleaseNULL(ivars->dispatchQueue); return; } /*! * @brief Create an instance of OSAction. * @discussion Methods to allocate an OSAction instance are generated for each method defined in a class with * a TYPE attribute, so there should not be any need to directly call OSAction::Create(). * @param target OSObject to receive the callback. This object will be retained until the OSAction is * canceled or freed. * @param targetmsgid Generated message ID for the target method. * @param msgid Generated message ID for the method invoked by the receiver of the OSAction * to generate the callback. * @param referenceSize Size of additional state structure available to the creator of the OSAction * with GetReference. * @param action Created OSAction with +1 retain count to be released by the caller. * @return kIOReturnSuccess on success. See IOReturn.h for error codes. */ // 3: Create an OSAction for the TimerOccurred method // THIS IS WHERE I NEED HELP OSAction* timerAction = nullptr; ret = OSAction::Create(this, 0, 0, 0, &timerAction); if (ret != kIOReturnSuccess) { LogErr("Failed to create OSAction: 0x%x", ret); goto cleanup; } // 4. Set handler ret = ivars->dispatchSource->SetHandler(timerAction); if (ret != kIOReturnSuccess) { LogErr("Failed to set handler: 0x%x", ret); goto cleanup; } // 5. Schedule timer (1 second) uint64_t deadline = mach_absolute_time() + NSEC_PER_SEC; ivars->dispatchSource->WakeAtTime(0, deadline, 0); cleanup: if (ret != kIOReturnSuccess) { OSSafeReleaseNULL(timerAction); OSSafeReleaseNULL(ivars->dispatchSource); OSSafeReleaseNULL(ivars->dispatchQueue); } } Problem: The code runs but the OSAction callback binding seems incorrect (Step 3). According to the OSAction documentation, I need to use the TYPE macro to properly bind the callback method. But I try to use TYPE(DRV_MAIN_CLASS_NAME::TimerOccurred) kern_return_t TimerOccurred() LOCALONLY; TYPE(TimerOccurred) kern_return_t TimerOccurred() LOCALONLY; kern_return_t TimerOccurred() TYPE(DRV_MAIN_CLASS_NAME::TimerOccurred) LOCALONLY; All results in Out-of-line definition of 'TimerOccurred' does not match any declaration in 'DRV_MAIN_CLASS_NAME' Questions: What is the correct way to declare a timer callback method using TYPE? How to get the values targetmsgid & msgid generated by Xcode? Any help would be greatly appreciated! Best Regards, Charles
Replies
6
Boosts
0
Views
1.2k
Activity
Apr ’25
Issue Writing to BAR1 After BAR0 is Unavailable
Hello Everyone, I encountered an issue with PCI memory access in DriverKit. In my case, BAR0 is not available, but BAR1 is ready for use. Here’s the log output: !!! ERROR : Failed to get BAR0 info (error: 0xe00002f0). !!! BAR1 - MemoryIndex: 0x00000000, Size: 0x00040000, Type: 0 Issue Description When I initially wrote to BAR0 using memoryIndex = 0, it worked successfully: AME_Address_Write_32(pAMEData, pAMEData->memoryIndex, AME_HOST_INT_MASK_REGISTER, 0x0F); However, I mistakenly forgot to update memoryIndex to 1 for BAR1. Surprisingly, the write operation still succeeded. When I fixed memoryIndex = 1 for BAR1, the write operation no longer had any effect. There was no error, but the expected behavior did not occur. Relevant API (From IOPCIDevice.iig) /*! /*! * @brief Writes a 32-bit value to the PCI device's aperture at a given memory index. * @discussion This method writes a 32-bit register on the device and returns its value. * @param memoryIndex An index into the array of ranges assigned to the device. * @param offset An offset into the device's memory specified by the index. * @param data A 32-bit value to be written in host byte order. */ void MemoryWrite32(uint8_t memoryIndex, uint64_t offset, uint32_t data) LOCALONLY; Log Output: Writes to BAR0 (memoryIndex = 0) AME_Address_Write_32() called memoryIndex: 0, offset: 0x34, data: 0xf Wrote data 0xF to offset 52 AME_Address_Write_32() called memoryIndex: 0, offset: 0xa0, data: 0x1 Wrote data 0x1 to offset 160 AME_Address_Write_32() called memoryIndex: 0, offset: 0x20, data: 0xffffffff Wrote data 0xFFFFFFFF to offset 32 Writes to BAR1 (memoryIndex = 1) – No Response AME_Address_Write_32() called memoryIndex: 1, offset: 0x34, data: 0xf No confirmation log, no visible effect. Questions What should memoryIndex be set to for BAR1? The log shows "BAR1 - MemoryIndex: 0x00000000", but should I be using 1 instead? How can I verify if a write operation to BAR1 is successful? Is there a way to check if the memory region is actually writable? Should I use MemoryRead32() to confirm the written value? Any guidance would be greatly appreciated! Best Regards, Charles
Replies
3
Boosts
1
Views
681
Activity
Mar ’25