Post

Replies

Boosts

Views

Activity

Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin! We finally broke through 20 Gbit/s and only at that speed did another problem become visible. It is not a blocker for us, but we hope you have a solution for it. Our DEXT moves each task's data between the IOBufferMemoryDescriptor from UserGetDataBuffer and its own shared ring. Tasks are 1 MiB. Both public API paths for that copy are expensive for us, though in different ways. A: map the descriptor. GetAddressRange, then memcpy. Every 1 MiB task causes 256 page faults in the DEXT (PROC_PIDTASKINFO pti_faults; the app takes almost none and the peer takes none, no COW faults, no pageins). 2047 KiB tasks cause 514. DEXT CPU per task: 0.62 ms user + 1.34 ms system (READ), 0.47 + 1.00 ms (WRITE). About 2 700 tasks/s with four copy queues, and CPU_Speed_Limit drops to around 60 during the run. B: kernel copy, no mapping. One IODMACommand per copy queue, Create(this, 0, {options 0, maxAddressBits 64}, &cmd). Per part: PrepareForDMA(0, hostDescriptor, offset, length, &flags, &count, segments), PerformOperation (OptionWrite for READ data, OptionRead for WRITE data, dmaOffset 0, length, ringOffset, ringDescriptor), CompleteDMA(0). All calls succeed, 582 435 parts, zero error returns, READ data verified (the test target discards WRITE data). The faults are gone, but PerformOperation takes about 1.375 ms of elapsed time per 1 MiB (prepare 10 µs, complete 6 µs) and DEXT CPU per task rises to 0.18 ms user + 3.29 ms system. Median READ at QD8 fell from 22.06 to 15.01 Gbit/s, WRITE from 20.39 to 15.84. That leaves us with three questions for you: Is there a supported way to access the task data without faulting every newly mapped page or copying through an intermediate buffer? For example a mapping that is populated up front (kIOMapPrefault is not among the public CreateMapping options and GetAddressRange takes no options). Is PerformOperation intended for this use case? Its implementation in the KDK appears to allocate an intermediate buffer and copy through it. Is there a way to copy between the two descriptors without that buffer? This looks like the same gap as in our already reported descriptor issues: every task still materializes a framework owned per task descriptor. Path A faults once per page of it, path B copies through an intermediate buffer. Does the planned software I/O path (task scoped, CPU accessible, multi page descriptor without hardware DMA preparation) cover this? If so, this measurement may be useful as the performance side of those reports. Thanks, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, We seem to have found another little problem in the kernel. It's not a showstopper but it would be good to get this fixed. We've been digging further into the latency spikes and traced one of the long stalls into the kernel submission path. I'd like to get your take before opening a Feedback report. Perhaps there's something we can do on the DEXT side that we're missing. Our iSCSI initiator is running on macOS against a loopback null target. The controller reports a maximum of 256 parallel tasks. We're using synchronous 1-MiB pread calls from multiple threads. QD here is the number of outstanding host requests, not the number of commands active on the wire. At QD1 to QD64, the latency already looked a bit off, but it wasn't obvious what was happening. We then deliberately pushed the test to QD512 and QD1024. That's when the long stalls really stood out and we could follow the affected thread into the kernel. Above the task-pool depth, one raw read doesn't return for roughly the entire load run, while the other threads keep completing thousands of requests: Host QD Requested load duration Longest raw read 256 10 s 203 ms 512 10 s 10,161 ms 512 20 s 20,271 ms 1024 10 s 10,504 ms 1024 20 s 20,507 ms Even at QD256, the longest read is "only" 203 ms, so it doesn't look nearly as dramatic at first glance. With workloads involving millions of block I/Os, though, even shorter recurring delays can matter. It was only at QD512 and QD1024 that the sustained, multi-second stall became unmistakable. We recorded the slow worker's OS thread ID and the exact start/end times of that read, then matched them to kernel stackshots. At QD1024/10 s, all 104 samples of that thread have this chain (leaving out the intermediate frames): pread IOSCSIProtocolServices::ExecuteCommand IOSCSIProtocolServices::SendSCSITasksFromQueue IOSCSIParallelInterfaceDevice::SendSCSICommand IOSCSIParallelInterfaceController::GetSCSIParallelTask IOCommandPool::getCommand IOCommandPool::gatedGetCommand The sampled PC in gatedGetCommand is the return address right after commandSleep. The 20-second runs show the same queue-draining path throughout the captured samples, mostly waiting for a task from the pool. We also checked the disassembly of the installed Kernel Collection, with the SCSI images matched to the KDK by UUID. ExecuteCommand calls the shared queue drainer inline. That loop keeps picking up more queued tasks. SendSCSICommand can wait for a free parallel task when called outside the workloop thread. Our reading is that one submitting thread ends up draining the queue for everyone else and gets held there as other callers keep adding work. Doubling the load duration from 10 to 20 seconds roughly doubles that read's latency too, which looks more like starvation under sustained load than a fixed timeout. One detail we haven't captured yet is exactly when that read's own SCSI task completes. Does that reading make sense to you or have we missed something? Is there a supported way to handle submission or completion in the DEXT that avoids holding up one caller like this while keeping the I/O concurrent? Or should I open a Feedback report? Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, We’re already using UserProcessBundledParallelTasks, with no single-task callbacks. We’re just not getting as much batching out of it as we’d like. In the latest run with roughly 1024 KiB I/O and host QD1 to 128, we counted about 3.21 million bundled callbacks for 3.26 million block requests: roughly 1.016 block requests per callback. That’s from the whole-run counters rather than a slot-count histogram. We also looked at the kernel path. Tasks go through serial preparation, including DMA preparation, before entering the batching queue. The delayed dispatch uses a fixed 10us interval. For now, though, we can live with it. Average RAW I/O latency at QD64 is around 20 to 30 ms. A bit annoying, but I guess it's OK. ;) We’d rather wait for the DMA fix before spending more time on batching. Being able to use larger I/O sizes again means fewer tasks for the same amount of data. That could reduce the overhead enough that the limited batching no longer matters much for us, even if the batches themselves stay small. Once that fix is available, we’ll take another look and see whether any further changes are needed at all. If they are, being able to adjust the 10us interval would be helpful. We have one or two other ideas as well, but we need to wait for the DMA fix before deciding whether they’re needed at all. Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, after a short vacation last week, here is a first small update for you, just so you don't think I have forgotten about you. We are still tuning, but here are a few early numbers already (Intel based). Well, if 1 Gbit already surprises you, then I do not know what you will say about these numbers. And a big compliment to the entire kernel developer team here, because it shows there is not just work behind this but also an enormous amount of know-how. Calibration and warmup: Data measurement (100-iteration loop): I am not sure how you would rate these, but to me they look good enough to keep. We also made a decision: the NULL driver goes into the initiator itself, as a built-in benchmark. That way the user can see right away whether something is the initiator or the network configuration. Thanks again for the pointer at exactly the right spot. Based on your 1 Gbit remark, the numbers should come as a surprise. That is also why the 'nDEXT Benchmark' will stay in the app. Sure, we can claim a lot here, but some people will surely want to verify it. They are welcome to and a matching measurement script is no big deal. I think any AI can put one together in two minutes. All in all we are happy with where this stands. The one thing we still want to get under control is the extreme outliers (compare High vs Low). As a side note, we have already broken the 20 Gbit/s mark, although it hits the CPU hard. In short, the DEXT can hold its own. Once this runs stably on Intel, we will move to the M5 for testing. Those still have delivery delays but they should (supposedly) arrive by the end of this week. I am curious. Best, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, A milestone update from my side: the world finally has an iSCSI initiator running on DEXT for macOS Thank you again for your genuinely helpful support throughout this. Data runs as it should up to 1 Gbit, and we're currently trying to win over a NAS vendor who can run the tests at 2.5 / 5 / 10 Gbit for us. We all agree internally that this kind of testing belongs in a lab, and we're curious to see whether someone will support us there. As we already discussed, I'll keep this thread open for now. If anything turns up during beta testing, we may come back to you here. I'll let you know once we go to release, and we're happy to close the DTS ticket at that point. Thanks again for all your help! Best regards from Germany, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, I sent another Feedback Assistant report: FB24597050 This one is about SCSI Task Management Functions (TMFs). For our iSCSI initiator this is certainly annoying, but the good news is that it is not a showstopper for our initial SOHO/NAS target. In that environment, normal READ/WRITE operation and the basic recovery paths are much more important to us right now. For enterprise use, however, I would consider proper TMF support essential. The interesting part is what we found in the KDK. As far as I can see, most of the DriverKit-side plumbing already exists. The generated User*TaskRequest IORPC paths are there for: Abort Task Abort Task Set Clear ACA Clear Task Set Logical Unit Reset Target Reset But the corresponding IOUserSCSIParallelInterfaceController kernel entry points currently just return kSCSIServiceResponse_FUNCTION_REJECTED instead of forwarding into those generated RPCs. So, from the outside, this looks less like a missing TMF architecture and more like a missing connection in the IOUserSCSIParallelInterfaceController kernel support layer. That is actually the encouraging part: the missing piece appears to be quite localized, and much of the infrastructure seems to already be there. One side note regarding BlockStorageDeviceDriverKit: TMF support does not appear to exist there at all, not even as an obvious equivalent API surface. For a SOHO/NAS-oriented software block device that may not matter much initially, but if that framework is ever intended to cover enterprise storage use cases as well, proper task-management and recovery semantics would probably need to be added there too. I wish you a relaxed and quiet week despite all this. ;) Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, first off, thanks a lot for your pointers, and yes, macOS now has a DEXT-based iSCSI initiator for NAS. There's still a bit left to do, but the connection holds and traffic runs at full tilt. What more could you want. And as promised, here are some numbers: Cache-independent RAW iSCSI measurements These are cache-independent RAW whole-disk measurements against a real 4.29 TB NAS-backed iSCSI LUN, RAW and unformatted target. Every run completed deterministic read-back verification with zero data mismatches, so the VERIFY column is a full end-to-end integrity pass over the whole transfer. Notes: Host queue depth was 256 for every run. The two 5G tests used sixteen 1 MiB requests, so the effective queue depth there was 16. Both 5G connections went straight to the NAS over independent mobile networks, no VPN. Throughput on those two rows is set by the radio path. What matters is that the initiator carries complete, read-back-verified iSCSI transfers over a live mobile link, the groundwork for our iSCSI-over-TLS layer in the initiator. The 400 GiB endurance run stayed at 111.592 MiB/s WRITE and 111.537 MiB/s READ, WRITE, READ and VERIFY each about 61 minutes, with a bounded DEXT proxy count throughout and no port-table growth. This is all running on Intel now, and running well on older Intel hardware specifically mattered to us: for something like this it has to stay performant on aging systems, not just the latest ones. From next week we move to Apple Silicon (M5 Pro) and see how throughput holds up there. The initiator itself is not fully done either, getting a DEXT driver cleanly embedded inside an application turned out to be a real challenge. If anything else turns up during beta testing, I'll post it here, though I doubt it will. One last thing, and I mean it. Thanks for the past few weeks. You revisited your own conclusions the moment the facts moved, and you went out of your way more than once, the entitlement request on our behalf being the clearest example. That kind of engagement is rare and made this a real pleasure. So thank you, Kevin. Enjoy your weekend, and until the next posting. :) Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, yes, I mean the initiator perspective, real iSCSI READ/WRITE over the network. Right now it is more annoying than technically critical. The important part is the DEXT does what it should, at least that is how it looks. I completely agree with you. If WRITE runs but READ does not run cleanly, this is almost certainly a code issue, not a framework issue. My personal guess is that it comes down to a wrong or missing request ID, READs and WRITEs getting in each other's way during lookup. But that is only a theory. We will see who is right in the end. The bets are on. 😉 Nonetheless, thank you for your continued support so far. That is not something I take for granted. Thank you again! I promise you, once it runs, you will be the first to know. I will send you valid numbers then. I am curious myself. Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, yes. At the API level the immediate cause was the lifetime of the IOBufferMemoryDescriptor returned by UserGetDataBuffer in our DEXT. Our explicit asynchronous retain and release accounting was already balanced, so the real work was to identify which proxy owned the growing Mach rights and to find the point where retirement was safe for deferred READ and WRITE tasks. Your observation that UserGetDataBuffer may have been intended primarily as a debugging aid turned out to be directly relevant. Under sustained I/O, the imported IOBufferMemoryDescriptor proxy and its lazily cached IOMemoryMap consumed Mach send rights per task until the DEXT approached port-table exhaustion. We isolated it in stages. lsmp snapshots around bounded I/O showed a strict 1:1 growth of IOBufferMemoryDescriptor and IOMemoryMap proxies. A purge-free 32 MiB write added 8,213 of each. They survived task completion, unmount, target removal and UserClient close. Only terminating the DEXT cleared the port table. Static analysis of the DriverKit runtime explained the pair: IOBufferMemoryDescriptor::GetAddressRange lazily calls CreateMapping and caches the IOMemoryMap on the descriptor. At refcount zero, IOBufferMemoryDescriptor::free() releases that map and the destruction path returns both Mach send rights. So the unit to retire is the descriptor proxy itself. Driving it to destruction releases the cached map with it. The safe retirement point came from the completion ordering in your open-source IOSCSIParallelFamily. SCSIParallelTask holds the task buffer in fDextDataBuffer. CompleteParallelTask releases that field before it calls CompleteSCSITask. Only after the return from ParallelTaskCompletion or BundledParallelTaskCompletion has the kernel-side SCSI task released its data-buffer ownership. Although it seems that this source is an older snapshot, we treated it as the lifetime model and confirmed the retirement point empirically against the current runtime. We treat the contract as empirical, derived from the SCSI-family ordering and confirmed against the runtime. The implementation now gives every successful UserGetDataBuffer import exactly one owner. We carry a single durable asynchronous hold through the single-task or bundled state and retire the proxy exactly once after framework completion returns, on a serial completion queue outside the data-path lock. With your ownership clarification, the expected retain count there is two. We release both, 2 to 1 to 0. Zero destroys the descriptor, releases its cached map and returns both send rights. The same exactly-once rule covers normal, inline, rejected, failed and bundled completion. If the count differs from the expected two, we release only our own hold and flag it in an anomaly counter. The result is port-flat under the load that previously exhausted the DEXT, with a 512 MiB write and cold readback byte-exact, GDBRETIRE unexpected=0, a stable PID and clean teardown and reconnect. The full lsmp captures and lifecycle analysis are attached to the last FA commit. The same work also updates our earlier read of the page-bound path. With direct raw-device I/O and cache bypass, WRITE now reaches about 111.7 MB/s over 2.4 GHz Wi-Fi. READ is currently held near 25 MB/s, about a 4.5x gap. WRITE shows the page-bound path can carry strong throughput, so the open limitation is now isolated to READ. We still have a small READ gap to close. I am fairly confident it sits on the iSCSI side. iSCSI is famously particular and rewards a clean implementation. With WRITE already at full speed, I read this as ours to solve, most likely in our own iSCSI handling. I would be surprised if DEXT or a framework issue were the cause. A clean DMA path would admittedly make it easier, but I am optimistic. We will see where it lands. One note on our own numbers, as a fair cautionary tale. Before we switched to raw-device I/O on the iSCSI LUN, we ran a test by copying a 4 GB file to the NAS over iSCSI via 2.4 GHz Wi-Fi. That test reported about 2.5 GB/s, completing in around 1.6 seconds, which is physically impossible over Wi-Fi. Two explanations were available: either we had quietly broken a few laws of physics and should start drafting a Nobel acceptance speech, or something went wrong with macOS, caching and/or RAM. We reluctantly chose option two and moved the harness to direct raw READ and WRITE on the iSCSI block device, bypassing the file system cache entirely. ;-) To find the remaining READ ceiling, we are wiring an end-to-end I/O flight recorder that timestamps and correlates each READ across SCSI task arrival, UserGetDataBuffer, ring publication, iSCSI Data-In reception, payload assembly, DEXT copy-back and framework completion. That should show whether the ceiling comes from buffer preparation, network scheduling, Data-In assembly, copy-back serialization or completion latency. Cross the fingers! Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Million times thanks to you!!! We finally got it & it's running! Hi Kevin, your last reply gave us the direction we needed. We have now validated a local, implementation-specific mitigation for the per-task proxy accumulation reported in FB24326124 In our current SCSIControllerDriverKit build, the DEXT port table remains essentially flat under the same page-sized workload that previously exhausted it and terminated the DEXT. The current validation result on our Intel test system is: 512 MiB of random data written to the mounted LUN unmount and remount before verification SHA-256 read back from the device matched the source DEXT PID remained unchanged throughout the test process port count was 111 before the transfer, 111 after WRITE and 116 after READ READ-back throughput was 219.4 MiB/s over WLAN The current WRITE timing is still affected by caching, so we are repeating that measurement with a cache-controlled test before publishing a number. The same build also completed clean unmount, target removal, UserClient close, reconnect and subsequent I/O. We are now extending the validation to longer soak tests and a substantially higher task-capacity configuration. The full lifecycle analysis, DriverKit runtime evidence and raw lsmp captures are already attached to FB24326124 I am keeping the public summary at the result level while the implementation remains under active validation. This gives us a viable interim path on the current public DriverKit surface. The underlying reports remain relevant: FB24326124 covers the per-task proxy lifecycle. FB23814092 covers the single-page software-I/O limitation. FB23814013 covers the missing CPU-accessible BlockStorage request descriptor. The current mitigation addresses the immediate stability blocker. It does not remove the additional per-page work imposed by the existing SCSI path, so an Apple-side fix would still provide a direct performance benefit without requiring another architectural change on our side. Right now this is a breakthrough. It works, but the full test matrix on Intel and Apple Silicon still has to be completed. At this stage it already looks like a more than viable solution until Apple has the final fixes in place. If the next soak tests remain clean, we will take a Developer ID build through notarization and begin a small, controlled external beta. Notarization would let us distribute broadly, but this driver writes at block level to live LUNs, so we will keep the initial rollout deliberately narrow and widen it only as field results allow. Now my final question to you: If any further SCSIControllerDriverKit bugs come up, including during the public beta, would it be fine with you if we post them to this thread? Thank you again to you and the SCSI team for continuing to investigate this. Best regards & enjoy your weekend!! Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, thanks, that matches what we ended up building: a tunable engine (I/O size, slot count) with single-page transfers and large slot counts, close to the single-page / massive-slot stress test you described. I filed FB24326124 with the full SDK, IIG, runtime-disassembly and lsmp evidence. Running that engine surfaced a second issue in the same per-task buffer path, beyond the UserGetDataBuffer overhead you flagged as the next bottleneck: a lifetime issue. For each data-bearing READ or WRITE SCSI task, the current path creates this proxy pair in the DEXT: UserGetDataBuffer -> one borrowed IOBufferMemoryDescriptor proxy GetAddressRange -> DriverKit CreateMapping -> one cached IOMemoryMap proxy The generated IIG reply carries one object, the IOBufferMemoryDescriptor. Static disassembly of the DriverKit runtime shows IOBufferMemoryDescriptor::GetAddressRange lazily calling IOMemoryDescriptor::CreateMapping and caching the returned IOMemoryMap on the descriptor. The normal destruction chain is present, but only runs at refcount 0: IOBufferMemoryDescriptor::free -> release cached IOMemoryMap -> OSObjectFree -> mach_port_deallocate The proxies remain live after final task completion. A purge-free 32 MiB write added 8,213 IOBufferMemoryDescriptor proxies and 8,213 IOMemoryMap proxies. The DEXT's own counter later reported: acquired=21061 released=21061 earlyDrop=0 gap=0 At the same point lsmp still showed 21,125 live proxies of each class. A teardown probe kept the same DEXT PID through volume unmount, target removal and UserClient close but the counts remained at 21,125 per class. DEXT process termination cleared the process port table. Because the DMA path fragments the workload into predominantly 4 KiB parents, the count rises quickly. In repeated runs the DEXT failed with its port count in the observed 260,000 to 290,000 range. Reconnecting the iSCSI session left operation unavailable in our test environment and only rebooting the Mac restored it. Two connections to your earlier notes: This is essentially the single-page / massive-slot engine you suggested as a stress test. It currently drives the DEXT into port-table exhaustion rather than only running slowly. Your read-path idea of parallel UserGetDataBuffer calls behind the SCSITask-table lock would leave the per-call lifetime unchanged. Any throughput gain would therefore bring the same port ceiling closer in time. Retiring the imported descriptor proxy once per task at a final state would let the existing destruction path reclaim both send rights without changing the public API. Does the software-I/O path you outlined already include that retirement, or would it be a separate lifecycle correction that could also be delivered in a macOS 26 software update for Intel? Thank you again to you and the SCSI team for continuing to look into the software-backed path. Best regards & enjoy your Weekend!!! Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Kevin, thank you very much for the detailed response and for taking this back to the SCSI team. The confirmation that the single-page limitation is a bug rather than an inherent limitation of SCSIControllerDriverKit is probably the most important result of this investigation for us. Even though the current limitation means that we cannot move forward with a production implementation at the moment, having the issue clearly identified and having a potential path to address it is a huge step forward. The software I/O approach you described makes a lot of sense for our use case. In particular, the possibility of handling this within IOUserSCSIParallelInterfaceController, without requiring a DriverKit API or ABI change, sounds like a very clean solution for software-backed SCSI controllers. Your suggestion regarding the bundled architecture and a very high maximum task count is also very helpful. As I understand it, this would allow us to reduce the delivery overhead and task pile-up caused by the page-sized requests by keeping substantially more work in flight and delivering tasks in bundles. We already have a highly parallel data path behind the DEXT, so this is something we can test without fundamentally changing the architecture. We see this primarily as a useful proof of technology. It may demonstrate how far the existing path can be pushed despite the page-sized task limitation and we will certainly test it. For a production iSCSI initiator, however, we would not consider this workaround a viable foundation. Bundling can reduce delivery and scheduling overhead, but it does not remove the underlying fragmentation. A 1 MiB I/O still becomes 256 separate 4 KiB SCSI tasks. At sustained storage throughput, that means processing very large numbers of tasks, completions and associated bookkeeping for I/O that would naturally be represented by far fewer larger operations. Even if sufficient parallelism makes the throughput look reasonable, the CPU cost, latency characteristics and scaling behavior would remain concerns for a commercial storage product. That is why the bug fix makes such a fundamental difference for us. It is not simply another performance optimization. It determines whether SCSIControllerDriverKit can provide a clean production data path for this type of software-backed controller rather than requiring us to optimize around artificial I/O fragmentation. Given that the potential software I/O solution may be contained within IOUserSCSIParallelInterfaceController, without requiring a DriverKit API or ABI change, may I ask two final planning questions? I completely understand that you cannot provide a roadmap or commit to a release date. From an engineering perspective, is this something you would reasonably expect could be addressed on a near-term macOS software update timescale, or should we plan for the current single-page limitation to remain for the foreseeable future? Also, since macOS Tahoe 26 is the final major macOS release supporting Intel Macs, could a fix of this kind potentially still reach macOS 26 and therefore Intel Macs, or is it more realistic to expect that the corrected software I/O path would only become available on Apple silicon? The timing distinction is particularly useful for us. If the underlying issue has a realistic prospect of being addressed, we would much rather avoid investing significant engineering effort into optimizing a proof-of-technology path around a limitation that may disappear. Regardless of the timing, thank you again to you and the entire SCSI team for taking the time to investigate this. We really appreciate the depth of the technical feedback and the effort that has gone into understanding this use case. Although we cannot take the production implementation much further with the current limitation, having the limitation confirmed as a bug and having a potential path forward is a fantastic outcome for us. We hope that the underlying issue can be addressed in the near future and we are very much looking forward to testing the proper software I/O path if and when it becomes available. Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Kevin, regarding your statement that SCSIControllerDriverKit is not intended for virtual or software-based controllers, I believe there might be a small additive path that makes it work without opening the DMA security wall. After reviewing Apple's public kernel source repository, the public SCSIControllerDriverKit interfaces and the behavior of our software-backed SCSI prototype, we believe there may be a relatively small architectural change that would support software storage controllers without opening or weakening Apple's DMA security boundary. We are aware that granting developers direct access to DMA is not a direction Apple wants to take. That's the reason for this "workaround idea". Core Idea Apple does not need to open the DMA security wall. It can keep that wall fully closed for software-backed controllers and still provide the missing native storage path. We currently call this the No-DMA Solution. By "No-DMA" we mean the data-path contract presented to the DriverKit developer. A software-backed controller does not program a physical DMA engine. It therefore does not require a DMA address, IOVA, IOMapper or access to a device-specific IOMMU domain. The framework would continue to own and enforce all memory authorization. Current Task Path Our understanding of the current task path is approximately: ProcessParallelTask → PrepareForDMA → GenerateIOVMSegments → validate segment geometry → UserProcessParallelTask → UserGetDataBuffer Once UserProcessParallelTask() is reached, the existing UserGetDataBuffer() path provides the IOBufferMemoryDescriptor that a CPU-based software controller needs. The problem is that a request whose original buffer produces more than one DMA/IOVM segment can fail before the DEXT callback is delivered. The driver therefore never gets the opportunity to use the CPU-accessible buffer. Apple's public interface already defines UserGetDataBuffer() as returning an IOBufferMemoryDescriptor for the task. Proposed Change The proposed change is an explicit, opt-in software-backend capability. The following code is only illustrative pseudocode. We do not know the internal class names or the most appropriate public API shape. 1. Add a Controller Capability // Illustrative API only. constexpr uint64_t kSCSIControllerOptionSoftwareBackend = 0x00000001ULL; The existing default would remain the current hardware-DMA behavior: virtual uint64_t GetControllerOptions() { return 0; } A software-backed controller would opt in explicitly: uint64_t GetControllerOptions() override { return kSCSIControllerOptionSoftwareBackend; } The same capability could alternatively be reported as an optional key through the existing UserReportHBAConstraints() contract if that fits the framework ABI better. 2. Add One Task-Admission Branch Conceptually, the framework-side change could be: if (controller->GetControllerOptions() & kSCSIControllerOptionSoftwareBackend) { /* * Keep the original task buffer under framework ownership. * * Establish the request-scoped CPU-accessible buffer used by * UserGetDataBuffer(). * * Do not prepare the task for a physical DMA engine. * Do not generate an IOVA for the software controller. * Do not reject the task based on DMA segment count. */ PrepareTaskBufferForCPUAccess(task); DispatchUserProcessParallelTaskOrBundle(task); } else { /* * Existing hardware-controller path, unchanged. */ PrepareForDMA(task); GenerateIOVMSegments(task); ValidateControllerDMALimits(task); DispatchUserProcessParallelTaskOrBundle(task); } 3. Reuse the Existing CPU-Buffer Path The software controller would continue using the public interfaces that already exist: IOBufferMemoryDescriptor *buffer = nullptr; IOAddressSegment range = {}; UserGetDataBuffer( targetID, controllerTaskIdentifier, &buffer ); buffer->GetAddressRange(&range); /* * CPU-based software processing: * iSCSI, NBD, encryption, compression, cloud storage, etc. */ ParallelTaskCompletion(...); Essential Contract The exact implementation may differ internally. The essential contract is only: Software-backed controller selected → no hardware-DMA preparation → no DMA segment-count admission gate → normal task delivery → complete request range available through UserGetDataBuffer → existing completion and cancellation semantics retained Existing hardware controllers would remain on the current path because they would not set the capability. For software-backed controllers, multi-page and page-straddling requests could then be delivered up to the controller's reported maximum transfer size without making physical DMA segment count part of the controller contract. Security Model The security model would remain narrowly request-scoped: no raw DMA address exposed no IOVA reverse mapping no public IOMapper access no physical-address access no access to another device's IOMMU domain The DEXT would receive only the buffer already authorized for the current task, with its range, direction and lifetime bound to that task. Access would end with completion, cancellation or teardown. Architectural Distinction Architecturally, this would create a clean distinction: Hardware-backed controller: request buffer → DMA preparation → IOMapper / IOVA → physical device Software-backed controller: request buffer → request-scoped CPU access → software transport or processing Immediate Use Case: iSCSI Initiator For our iSCSI initiator the resulting path would be: macOS SCSI task → UserProcessParallelTask → UserGetDataBuffer → software iSCSI transport → ParallelTaskCompletion This would remove the current dependency on one DMA/IOVM segment while leaving Apple's DMA isolation fully intact. (Tadaaaa - sounds like a great Jackpot) Although iSCSI is our immediate use case, the same capability would also support software-defined block storage, NBD, cloud-backed disks, encryption, compression, deduplication and virtual test controllers. Scope Assessment We obviously do not know whether the internal framework is structured exactly as the pseudocode suggests, or whether the existing UserGetDataBuffer() backing buffer currently depends on part of the DMA-preparation path. We also do not expect the API names above to be adopted literally. The architectural change nevertheless appears local and additive: one opt-in controller capability one task-admission distinction one request-buffer lifetime guarantee If this approach proves successful for SCSIControllerDriverKit, a similar concept could likely be applied to BlockStorageDeviceDriverKit as well though from our perspective, that would require a significantly larger effort. With DriverKit 27 currently in beta, we thought this might be a useful time to raise the idea & it's the easiest solution. ;) Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, thanks again for the detailed explanation and for requesting the SCSIControllerDriverKit entitlement. Your last reply makes the architectural situation much clearer. In particular, the point that neither BlockStorageDeviceDriverKit nor SCSIControllerDriverKit was originally designed with software-backed I/O in mind explains very well why we keep ending up at the same boundary from two different directions. I’m now trying to make a practical product decision and would appreciate your technical read. For a pure software iSCSI initiator, BlockStorageDeviceDriverKit still seems like the conceptually natural fit: there is no PCI/Thunderbolt device and no hardware DMA target. The block-device model itself already gives us what we need. The missing piece is a request-scoped, CPU-accessible memory descriptor for DoAsyncReadWrite instead of only dmaAddr. From the outside, this looks like a relatively contained addition to the existing BlockStorage I/O path, particularly since the underlying I/O path already has the memory descriptor needed to establish the DMA mapping. I fully appreciate that the implementation, ABI, security and validation work on your side may make it considerably less trivial than it appears from the API boundary. SCSIControllerDriverKit works functionally, which is an important result, but the current page-bound path is not viable for our product. Even if that limitation is fixed first, SCSI remains an adaptation of a hardware-controller interface to a software-backed device rather than the natural abstraction for it. So we are effectively choosing between three paths: • continue investing in SCSI and wait for the larger-I/O fix you mentioned; • wait for a proper CPU-accessible BlockStorage I/O path; • ship a classic KEXT, which on Apple Silicon means requiring Reduced Security. The third option is the one I would most like to avoid. Apart from the security implications, asking users to lower their Mac’s security policy simply to access an iSCSI LUN is a poor installation experience. At the same time, we need to make a realistic engineering decision. If there is a realistic prospect of BlockStorage gaining such a path in a future macOS release, investing significant effort into a new KEXT may make very little sense. I understand that you cannot provide a roadmap or promise a timeframe; even a qualitative indication of whether this is something worth designing around would be extremely helpful. What I’m really asking is for your technical intuition: given what you now know about this use case and the current implementation, is BlockStorage support for software-backed I/O something you could realistically see being addressed in a future macOS release, or should we plan on the assumption that SCSI is the only DriverKit path for the foreseeable future, with a KEXT as the fallback if its performance limitations cannot be resolved? If additional measurements, a minimal reproducer or anything else attached to FB23814013 would help make the case internally, I’m very happy to provide it. Thanks again, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin! We finally broke through 20 Gbit/s and only at that speed did another problem become visible. It is not a blocker for us, but we hope you have a solution for it. Our DEXT moves each task's data between the IOBufferMemoryDescriptor from UserGetDataBuffer and its own shared ring. Tasks are 1 MiB. Both public API paths for that copy are expensive for us, though in different ways. A: map the descriptor. GetAddressRange, then memcpy. Every 1 MiB task causes 256 page faults in the DEXT (PROC_PIDTASKINFO pti_faults; the app takes almost none and the peer takes none, no COW faults, no pageins). 2047 KiB tasks cause 514. DEXT CPU per task: 0.62 ms user + 1.34 ms system (READ), 0.47 + 1.00 ms (WRITE). About 2 700 tasks/s with four copy queues, and CPU_Speed_Limit drops to around 60 during the run. B: kernel copy, no mapping. One IODMACommand per copy queue, Create(this, 0, {options 0, maxAddressBits 64}, &cmd). Per part: PrepareForDMA(0, hostDescriptor, offset, length, &flags, &count, segments), PerformOperation (OptionWrite for READ data, OptionRead for WRITE data, dmaOffset 0, length, ringOffset, ringDescriptor), CompleteDMA(0). All calls succeed, 582 435 parts, zero error returns, READ data verified (the test target discards WRITE data). The faults are gone, but PerformOperation takes about 1.375 ms of elapsed time per 1 MiB (prepare 10 µs, complete 6 µs) and DEXT CPU per task rises to 0.18 ms user + 3.29 ms system. Median READ at QD8 fell from 22.06 to 15.01 Gbit/s, WRITE from 20.39 to 15.84. That leaves us with three questions for you: Is there a supported way to access the task data without faulting every newly mapped page or copying through an intermediate buffer? For example a mapping that is populated up front (kIOMapPrefault is not among the public CreateMapping options and GetAddressRange takes no options). Is PerformOperation intended for this use case? Its implementation in the KDK appears to allocate an intermediate buffer and copy through it. Is there a way to copy between the two descriptors without that buffer? This looks like the same gap as in our already reported descriptor issues: every task still materializes a framework owned per task descriptor. Path A faults once per page of it, path B copies through an intermediate buffer. Does the planned software I/O path (task scoped, CPU accessible, multi page descriptor without hardware DMA preparation) cover this? If so, this measurement may be useful as the performance side of those reports. Thanks, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, We seem to have found another little problem in the kernel. It's not a showstopper but it would be good to get this fixed. We've been digging further into the latency spikes and traced one of the long stalls into the kernel submission path. I'd like to get your take before opening a Feedback report. Perhaps there's something we can do on the DEXT side that we're missing. Our iSCSI initiator is running on macOS against a loopback null target. The controller reports a maximum of 256 parallel tasks. We're using synchronous 1-MiB pread calls from multiple threads. QD here is the number of outstanding host requests, not the number of commands active on the wire. At QD1 to QD64, the latency already looked a bit off, but it wasn't obvious what was happening. We then deliberately pushed the test to QD512 and QD1024. That's when the long stalls really stood out and we could follow the affected thread into the kernel. Above the task-pool depth, one raw read doesn't return for roughly the entire load run, while the other threads keep completing thousands of requests: Host QD Requested load duration Longest raw read 256 10 s 203 ms 512 10 s 10,161 ms 512 20 s 20,271 ms 1024 10 s 10,504 ms 1024 20 s 20,507 ms Even at QD256, the longest read is "only" 203 ms, so it doesn't look nearly as dramatic at first glance. With workloads involving millions of block I/Os, though, even shorter recurring delays can matter. It was only at QD512 and QD1024 that the sustained, multi-second stall became unmistakable. We recorded the slow worker's OS thread ID and the exact start/end times of that read, then matched them to kernel stackshots. At QD1024/10 s, all 104 samples of that thread have this chain (leaving out the intermediate frames): pread IOSCSIProtocolServices::ExecuteCommand IOSCSIProtocolServices::SendSCSITasksFromQueue IOSCSIParallelInterfaceDevice::SendSCSICommand IOSCSIParallelInterfaceController::GetSCSIParallelTask IOCommandPool::getCommand IOCommandPool::gatedGetCommand The sampled PC in gatedGetCommand is the return address right after commandSleep. The 20-second runs show the same queue-draining path throughout the captured samples, mostly waiting for a task from the pool. We also checked the disassembly of the installed Kernel Collection, with the SCSI images matched to the KDK by UUID. ExecuteCommand calls the shared queue drainer inline. That loop keeps picking up more queued tasks. SendSCSICommand can wait for a free parallel task when called outside the workloop thread. Our reading is that one submitting thread ends up draining the queue for everyone else and gets held there as other callers keep adding work. Doubling the load duration from 10 to 20 seconds roughly doubles that read's latency too, which looks more like starvation under sustained load than a fixed timeout. One detail we haven't captured yet is exactly when that read's own SCSI task completes. Does that reading make sense to you or have we missed something? Is there a supported way to handle submission or completion in the DEXT that avoids holding up one caller like this while keeping the I/O concurrent? Or should I open a Feedback report? Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, We’re already using UserProcessBundledParallelTasks, with no single-task callbacks. We’re just not getting as much batching out of it as we’d like. In the latest run with roughly 1024 KiB I/O and host QD1 to 128, we counted about 3.21 million bundled callbacks for 3.26 million block requests: roughly 1.016 block requests per callback. That’s from the whole-run counters rather than a slot-count histogram. We also looked at the kernel path. Tasks go through serial preparation, including DMA preparation, before entering the batching queue. The delayed dispatch uses a fixed 10us interval. For now, though, we can live with it. Average RAW I/O latency at QD64 is around 20 to 30 ms. A bit annoying, but I guess it's OK. ;) We’d rather wait for the DMA fix before spending more time on batching. Being able to use larger I/O sizes again means fewer tasks for the same amount of data. That could reduce the overhead enough that the limited batching no longer matters much for us, even if the batches themselves stay small. Once that fix is available, we’ll take another look and see whether any further changes are needed at all. If they are, being able to adjust the 10us interval would be helpful. We have one or two other ideas as well, but we need to wait for the DMA fix before deciding whether they’re needed at all. Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, after a short vacation last week, here is a first small update for you, just so you don't think I have forgotten about you. We are still tuning, but here are a few early numbers already (Intel based). Well, if 1 Gbit already surprises you, then I do not know what you will say about these numbers. And a big compliment to the entire kernel developer team here, because it shows there is not just work behind this but also an enormous amount of know-how. Calibration and warmup: Data measurement (100-iteration loop): I am not sure how you would rate these, but to me they look good enough to keep. We also made a decision: the NULL driver goes into the initiator itself, as a built-in benchmark. That way the user can see right away whether something is the initiator or the network configuration. Thanks again for the pointer at exactly the right spot. Based on your 1 Gbit remark, the numbers should come as a surprise. That is also why the 'nDEXT Benchmark' will stay in the app. Sure, we can claim a lot here, but some people will surely want to verify it. They are welcome to and a matching measurement script is no big deal. I think any AI can put one together in two minutes. All in all we are happy with where this stands. The one thing we still want to get under control is the extreme outliers (compare High vs Low). As a side note, we have already broken the 20 Gbit/s mark, although it hits the CPU hard. In short, the DEXT can hold its own. Once this runs stably on Intel, we will move to the M5 for testing. Those still have delivery delays but they should (supposedly) arrive by the end of this week. I am curious. Best, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, A milestone update from my side: the world finally has an iSCSI initiator running on DEXT for macOS Thank you again for your genuinely helpful support throughout this. Data runs as it should up to 1 Gbit, and we're currently trying to win over a NAS vendor who can run the tests at 2.5 / 5 / 10 Gbit for us. We all agree internally that this kind of testing belongs in a lab, and we're curious to see whether someone will support us there. As we already discussed, I'll keep this thread open for now. If anything turns up during beta testing, we may come back to you here. I'll let you know once we go to release, and we're happy to close the DTS ticket at that point. Thanks again for all your help! Best regards from Germany, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Sep ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, I sent another Feedback Assistant report: FB24597050 This one is about SCSI Task Management Functions (TMFs). For our iSCSI initiator this is certainly annoying, but the good news is that it is not a showstopper for our initial SOHO/NAS target. In that environment, normal READ/WRITE operation and the basic recovery paths are much more important to us right now. For enterprise use, however, I would consider proper TMF support essential. The interesting part is what we found in the KDK. As far as I can see, most of the DriverKit-side plumbing already exists. The generated User*TaskRequest IORPC paths are there for: Abort Task Abort Task Set Clear ACA Clear Task Set Logical Unit Reset Target Reset But the corresponding IOUserSCSIParallelInterfaceController kernel entry points currently just return kSCSIServiceResponse_FUNCTION_REJECTED instead of forwarding into those generated RPCs. So, from the outside, this looks less like a missing TMF architecture and more like a missing connection in the IOUserSCSIParallelInterfaceController kernel support layer. That is actually the encouraging part: the missing piece appears to be quite localized, and much of the infrastructure seems to already be there. One side note regarding BlockStorageDeviceDriverKit: TMF support does not appear to exist there at all, not even as an obvious equivalent API surface. For a SOHO/NAS-oriented software block device that may not matter much initially, but if that framework is ever intended to cover enterprise storage use cases as well, proper task-management and recovery semantics would probably need to be added there too. I wish you a relaxed and quiet week despite all this. ;) Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, first off, thanks a lot for your pointers, and yes, macOS now has a DEXT-based iSCSI initiator for NAS. There's still a bit left to do, but the connection holds and traffic runs at full tilt. What more could you want. And as promised, here are some numbers: Cache-independent RAW iSCSI measurements These are cache-independent RAW whole-disk measurements against a real 4.29 TB NAS-backed iSCSI LUN, RAW and unformatted target. Every run completed deterministic read-back verification with zero data mismatches, so the VERIFY column is a full end-to-end integrity pass over the whole transfer. Notes: Host queue depth was 256 for every run. The two 5G tests used sixteen 1 MiB requests, so the effective queue depth there was 16. Both 5G connections went straight to the NAS over independent mobile networks, no VPN. Throughput on those two rows is set by the radio path. What matters is that the initiator carries complete, read-back-verified iSCSI transfers over a live mobile link, the groundwork for our iSCSI-over-TLS layer in the initiator. The 400 GiB endurance run stayed at 111.592 MiB/s WRITE and 111.537 MiB/s READ, WRITE, READ and VERIFY each about 61 minutes, with a bounded DEXT proxy count throughout and no port-table growth. This is all running on Intel now, and running well on older Intel hardware specifically mattered to us: for something like this it has to stay performant on aging systems, not just the latest ones. From next week we move to Apple Silicon (M5 Pro) and see how throughput holds up there. The initiator itself is not fully done either, getting a DEXT driver cleanly embedded inside an application turned out to be a real challenge. If anything else turns up during beta testing, I'll post it here, though I doubt it will. One last thing, and I mean it. Thanks for the past few weeks. You revisited your own conclusions the moment the facts moved, and you went out of your way more than once, the entitlement request on our behalf being the clearest example. That kind of engagement is rare and made this a real pleasure. So thank you, Kevin. Enjoy your weekend, and until the next posting. :) Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, yes, I mean the initiator perspective, real iSCSI READ/WRITE over the network. Right now it is more annoying than technically critical. The important part is the DEXT does what it should, at least that is how it looks. I completely agree with you. If WRITE runs but READ does not run cleanly, this is almost certainly a code issue, not a framework issue. My personal guess is that it comes down to a wrong or missing request ID, READs and WRITEs getting in each other's way during lookup. But that is only a theory. We will see who is right in the end. The bets are on. 😉 Nonetheless, thank you for your continued support so far. That is not something I take for granted. Thank you again! I promise you, once it runs, you will be the first to know. I will send you valid numbers then. I am curious myself. Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, yes. At the API level the immediate cause was the lifetime of the IOBufferMemoryDescriptor returned by UserGetDataBuffer in our DEXT. Our explicit asynchronous retain and release accounting was already balanced, so the real work was to identify which proxy owned the growing Mach rights and to find the point where retirement was safe for deferred READ and WRITE tasks. Your observation that UserGetDataBuffer may have been intended primarily as a debugging aid turned out to be directly relevant. Under sustained I/O, the imported IOBufferMemoryDescriptor proxy and its lazily cached IOMemoryMap consumed Mach send rights per task until the DEXT approached port-table exhaustion. We isolated it in stages. lsmp snapshots around bounded I/O showed a strict 1:1 growth of IOBufferMemoryDescriptor and IOMemoryMap proxies. A purge-free 32 MiB write added 8,213 of each. They survived task completion, unmount, target removal and UserClient close. Only terminating the DEXT cleared the port table. Static analysis of the DriverKit runtime explained the pair: IOBufferMemoryDescriptor::GetAddressRange lazily calls CreateMapping and caches the IOMemoryMap on the descriptor. At refcount zero, IOBufferMemoryDescriptor::free() releases that map and the destruction path returns both Mach send rights. So the unit to retire is the descriptor proxy itself. Driving it to destruction releases the cached map with it. The safe retirement point came from the completion ordering in your open-source IOSCSIParallelFamily. SCSIParallelTask holds the task buffer in fDextDataBuffer. CompleteParallelTask releases that field before it calls CompleteSCSITask. Only after the return from ParallelTaskCompletion or BundledParallelTaskCompletion has the kernel-side SCSI task released its data-buffer ownership. Although it seems that this source is an older snapshot, we treated it as the lifetime model and confirmed the retirement point empirically against the current runtime. We treat the contract as empirical, derived from the SCSI-family ordering and confirmed against the runtime. The implementation now gives every successful UserGetDataBuffer import exactly one owner. We carry a single durable asynchronous hold through the single-task or bundled state and retire the proxy exactly once after framework completion returns, on a serial completion queue outside the data-path lock. With your ownership clarification, the expected retain count there is two. We release both, 2 to 1 to 0. Zero destroys the descriptor, releases its cached map and returns both send rights. The same exactly-once rule covers normal, inline, rejected, failed and bundled completion. If the count differs from the expected two, we release only our own hold and flag it in an anomaly counter. The result is port-flat under the load that previously exhausted the DEXT, with a 512 MiB write and cold readback byte-exact, GDBRETIRE unexpected=0, a stable PID and clean teardown and reconnect. The full lsmp captures and lifecycle analysis are attached to the last FA commit. The same work also updates our earlier read of the page-bound path. With direct raw-device I/O and cache bypass, WRITE now reaches about 111.7 MB/s over 2.4 GHz Wi-Fi. READ is currently held near 25 MB/s, about a 4.5x gap. WRITE shows the page-bound path can carry strong throughput, so the open limitation is now isolated to READ. We still have a small READ gap to close. I am fairly confident it sits on the iSCSI side. iSCSI is famously particular and rewards a clean implementation. With WRITE already at full speed, I read this as ours to solve, most likely in our own iSCSI handling. I would be surprised if DEXT or a framework issue were the cause. A clean DMA path would admittedly make it easier, but I am optimistic. We will see where it lands. One note on our own numbers, as a fair cautionary tale. Before we switched to raw-device I/O on the iSCSI LUN, we ran a test by copying a 4 GB file to the NAS over iSCSI via 2.4 GHz Wi-Fi. That test reported about 2.5 GB/s, completing in around 1.6 seconds, which is physically impossible over Wi-Fi. Two explanations were available: either we had quietly broken a few laws of physics and should start drafting a Nobel acceptance speech, or something went wrong with macOS, caching and/or RAM. We reluctantly chose option two and moved the harness to direct raw READ and WRITE on the iSCSI block device, bypassing the file system cache entirely. ;-) To find the remaining READ ceiling, we are wiring an end-to-end I/O flight recorder that timestamps and correlates each READ across SCSI task arrival, UserGetDataBuffer, ring publication, iSCSI Data-In reception, payload assembly, DEXT copy-back and framework completion. That should show whether the ceiling comes from buffer preparation, network scheduling, Data-In assembly, copy-back serialization or completion latency. Cross the fingers! Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Million times thanks to you!!! We finally got it & it's running! Hi Kevin, your last reply gave us the direction we needed. We have now validated a local, implementation-specific mitigation for the per-task proxy accumulation reported in FB24326124 In our current SCSIControllerDriverKit build, the DEXT port table remains essentially flat under the same page-sized workload that previously exhausted it and terminated the DEXT. The current validation result on our Intel test system is: 512 MiB of random data written to the mounted LUN unmount and remount before verification SHA-256 read back from the device matched the source DEXT PID remained unchanged throughout the test process port count was 111 before the transfer, 111 after WRITE and 116 after READ READ-back throughput was 219.4 MiB/s over WLAN The current WRITE timing is still affected by caching, so we are repeating that measurement with a cache-controlled test before publishing a number. The same build also completed clean unmount, target removal, UserClient close, reconnect and subsequent I/O. We are now extending the validation to longer soak tests and a substantially higher task-capacity configuration. The full lifecycle analysis, DriverKit runtime evidence and raw lsmp captures are already attached to FB24326124 I am keeping the public summary at the result level while the implementation remains under active validation. This gives us a viable interim path on the current public DriverKit surface. The underlying reports remain relevant: FB24326124 covers the per-task proxy lifecycle. FB23814092 covers the single-page software-I/O limitation. FB23814013 covers the missing CPU-accessible BlockStorage request descriptor. The current mitigation addresses the immediate stability blocker. It does not remove the additional per-page work imposed by the existing SCSI path, so an Apple-side fix would still provide a direct performance benefit without requiring another architectural change on our side. Right now this is a breakthrough. It works, but the full test matrix on Intel and Apple Silicon still has to be completed. At this stage it already looks like a more than viable solution until Apple has the final fixes in place. If the next soak tests remain clean, we will take a Developer ID build through notarization and begin a small, controlled external beta. Notarization would let us distribute broadly, but this driver writes at block level to live LUNs, so we will keep the initial rollout deliberately narrow and widen it only as field results allow. Now my final question to you: If any further SCSIControllerDriverKit bugs come up, including during the public beta, would it be fine with you if we post them to this thread? Thank you again to you and the SCSI team for continuing to investigate this. Best regards & enjoy your weekend!! Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, thanks, that matches what we ended up building: a tunable engine (I/O size, slot count) with single-page transfers and large slot counts, close to the single-page / massive-slot stress test you described. I filed FB24326124 with the full SDK, IIG, runtime-disassembly and lsmp evidence. Running that engine surfaced a second issue in the same per-task buffer path, beyond the UserGetDataBuffer overhead you flagged as the next bottleneck: a lifetime issue. For each data-bearing READ or WRITE SCSI task, the current path creates this proxy pair in the DEXT: UserGetDataBuffer -> one borrowed IOBufferMemoryDescriptor proxy GetAddressRange -> DriverKit CreateMapping -> one cached IOMemoryMap proxy The generated IIG reply carries one object, the IOBufferMemoryDescriptor. Static disassembly of the DriverKit runtime shows IOBufferMemoryDescriptor::GetAddressRange lazily calling IOMemoryDescriptor::CreateMapping and caching the returned IOMemoryMap on the descriptor. The normal destruction chain is present, but only runs at refcount 0: IOBufferMemoryDescriptor::free -> release cached IOMemoryMap -> OSObjectFree -> mach_port_deallocate The proxies remain live after final task completion. A purge-free 32 MiB write added 8,213 IOBufferMemoryDescriptor proxies and 8,213 IOMemoryMap proxies. The DEXT's own counter later reported: acquired=21061 released=21061 earlyDrop=0 gap=0 At the same point lsmp still showed 21,125 live proxies of each class. A teardown probe kept the same DEXT PID through volume unmount, target removal and UserClient close but the counts remained at 21,125 per class. DEXT process termination cleared the process port table. Because the DMA path fragments the workload into predominantly 4 KiB parents, the count rises quickly. In repeated runs the DEXT failed with its port count in the observed 260,000 to 290,000 range. Reconnecting the iSCSI session left operation unavailable in our test environment and only rebooting the Mac restored it. Two connections to your earlier notes: This is essentially the single-page / massive-slot engine you suggested as a stress test. It currently drives the DEXT into port-table exhaustion rather than only running slowly. Your read-path idea of parallel UserGetDataBuffer calls behind the SCSITask-table lock would leave the per-call lifetime unchanged. Any throughput gain would therefore bring the same port ceiling closer in time. Retiring the imported descriptor proxy once per task at a final state would let the existing destruction path reclaim both send rights without changing the public API. Does the software-I/O path you outlined already include that retirement, or would it be a separate lifecycle correction that could also be delivered in a macOS 26 software update for Intel? Thank you again to you and the SCSI team for continuing to look into the software-backed path. Best regards & enjoy your Weekend!!! Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Kevin, thank you very much for the detailed response and for taking this back to the SCSI team. The confirmation that the single-page limitation is a bug rather than an inherent limitation of SCSIControllerDriverKit is probably the most important result of this investigation for us. Even though the current limitation means that we cannot move forward with a production implementation at the moment, having the issue clearly identified and having a potential path to address it is a huge step forward. The software I/O approach you described makes a lot of sense for our use case. In particular, the possibility of handling this within IOUserSCSIParallelInterfaceController, without requiring a DriverKit API or ABI change, sounds like a very clean solution for software-backed SCSI controllers. Your suggestion regarding the bundled architecture and a very high maximum task count is also very helpful. As I understand it, this would allow us to reduce the delivery overhead and task pile-up caused by the page-sized requests by keeping substantially more work in flight and delivering tasks in bundles. We already have a highly parallel data path behind the DEXT, so this is something we can test without fundamentally changing the architecture. We see this primarily as a useful proof of technology. It may demonstrate how far the existing path can be pushed despite the page-sized task limitation and we will certainly test it. For a production iSCSI initiator, however, we would not consider this workaround a viable foundation. Bundling can reduce delivery and scheduling overhead, but it does not remove the underlying fragmentation. A 1 MiB I/O still becomes 256 separate 4 KiB SCSI tasks. At sustained storage throughput, that means processing very large numbers of tasks, completions and associated bookkeeping for I/O that would naturally be represented by far fewer larger operations. Even if sufficient parallelism makes the throughput look reasonable, the CPU cost, latency characteristics and scaling behavior would remain concerns for a commercial storage product. That is why the bug fix makes such a fundamental difference for us. It is not simply another performance optimization. It determines whether SCSIControllerDriverKit can provide a clean production data path for this type of software-backed controller rather than requiring us to optimize around artificial I/O fragmentation. Given that the potential software I/O solution may be contained within IOUserSCSIParallelInterfaceController, without requiring a DriverKit API or ABI change, may I ask two final planning questions? I completely understand that you cannot provide a roadmap or commit to a release date. From an engineering perspective, is this something you would reasonably expect could be addressed on a near-term macOS software update timescale, or should we plan for the current single-page limitation to remain for the foreseeable future? Also, since macOS Tahoe 26 is the final major macOS release supporting Intel Macs, could a fix of this kind potentially still reach macOS 26 and therefore Intel Macs, or is it more realistic to expect that the corrected software I/O path would only become available on Apple silicon? The timing distinction is particularly useful for us. If the underlying issue has a realistic prospect of being addressed, we would much rather avoid investing significant engineering effort into optimizing a proof-of-technology path around a limitation that may disappear. Regardless of the timing, thank you again to you and the entire SCSI team for taking the time to investigate this. We really appreciate the depth of the technical feedback and the effort that has gone into understanding this use case. Although we cannot take the production implementation much further with the current limitation, having the limitation confirmed as a bug and having a potential path forward is a fantastic outcome for us. We hope that the underlying issue can be addressed in the near future and we are very much looking forward to testing the proper software I/O path if and when it becomes available. Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Kevin, regarding your statement that SCSIControllerDriverKit is not intended for virtual or software-based controllers, I believe there might be a small additive path that makes it work without opening the DMA security wall. After reviewing Apple's public kernel source repository, the public SCSIControllerDriverKit interfaces and the behavior of our software-backed SCSI prototype, we believe there may be a relatively small architectural change that would support software storage controllers without opening or weakening Apple's DMA security boundary. We are aware that granting developers direct access to DMA is not a direction Apple wants to take. That's the reason for this "workaround idea". Core Idea Apple does not need to open the DMA security wall. It can keep that wall fully closed for software-backed controllers and still provide the missing native storage path. We currently call this the No-DMA Solution. By "No-DMA" we mean the data-path contract presented to the DriverKit developer. A software-backed controller does not program a physical DMA engine. It therefore does not require a DMA address, IOVA, IOMapper or access to a device-specific IOMMU domain. The framework would continue to own and enforce all memory authorization. Current Task Path Our understanding of the current task path is approximately: ProcessParallelTask → PrepareForDMA → GenerateIOVMSegments → validate segment geometry → UserProcessParallelTask → UserGetDataBuffer Once UserProcessParallelTask() is reached, the existing UserGetDataBuffer() path provides the IOBufferMemoryDescriptor that a CPU-based software controller needs. The problem is that a request whose original buffer produces more than one DMA/IOVM segment can fail before the DEXT callback is delivered. The driver therefore never gets the opportunity to use the CPU-accessible buffer. Apple's public interface already defines UserGetDataBuffer() as returning an IOBufferMemoryDescriptor for the task. Proposed Change The proposed change is an explicit, opt-in software-backend capability. The following code is only illustrative pseudocode. We do not know the internal class names or the most appropriate public API shape. 1. Add a Controller Capability // Illustrative API only. constexpr uint64_t kSCSIControllerOptionSoftwareBackend = 0x00000001ULL; The existing default would remain the current hardware-DMA behavior: virtual uint64_t GetControllerOptions() { return 0; } A software-backed controller would opt in explicitly: uint64_t GetControllerOptions() override { return kSCSIControllerOptionSoftwareBackend; } The same capability could alternatively be reported as an optional key through the existing UserReportHBAConstraints() contract if that fits the framework ABI better. 2. Add One Task-Admission Branch Conceptually, the framework-side change could be: if (controller->GetControllerOptions() & kSCSIControllerOptionSoftwareBackend) { /* * Keep the original task buffer under framework ownership. * * Establish the request-scoped CPU-accessible buffer used by * UserGetDataBuffer(). * * Do not prepare the task for a physical DMA engine. * Do not generate an IOVA for the software controller. * Do not reject the task based on DMA segment count. */ PrepareTaskBufferForCPUAccess(task); DispatchUserProcessParallelTaskOrBundle(task); } else { /* * Existing hardware-controller path, unchanged. */ PrepareForDMA(task); GenerateIOVMSegments(task); ValidateControllerDMALimits(task); DispatchUserProcessParallelTaskOrBundle(task); } 3. Reuse the Existing CPU-Buffer Path The software controller would continue using the public interfaces that already exist: IOBufferMemoryDescriptor *buffer = nullptr; IOAddressSegment range = {}; UserGetDataBuffer( targetID, controllerTaskIdentifier, &buffer ); buffer->GetAddressRange(&range); /* * CPU-based software processing: * iSCSI, NBD, encryption, compression, cloud storage, etc. */ ParallelTaskCompletion(...); Essential Contract The exact implementation may differ internally. The essential contract is only: Software-backed controller selected → no hardware-DMA preparation → no DMA segment-count admission gate → normal task delivery → complete request range available through UserGetDataBuffer → existing completion and cancellation semantics retained Existing hardware controllers would remain on the current path because they would not set the capability. For software-backed controllers, multi-page and page-straddling requests could then be delivered up to the controller's reported maximum transfer size without making physical DMA segment count part of the controller contract. Security Model The security model would remain narrowly request-scoped: no raw DMA address exposed no IOVA reverse mapping no public IOMapper access no physical-address access no access to another device's IOMMU domain The DEXT would receive only the buffer already authorized for the current task, with its range, direction and lifetime bound to that task. Access would end with completion, cancellation or teardown. Architectural Distinction Architecturally, this would create a clean distinction: Hardware-backed controller: request buffer → DMA preparation → IOMapper / IOVA → physical device Software-backed controller: request buffer → request-scoped CPU access → software transport or processing Immediate Use Case: iSCSI Initiator For our iSCSI initiator the resulting path would be: macOS SCSI task → UserProcessParallelTask → UserGetDataBuffer → software iSCSI transport → ParallelTaskCompletion This would remove the current dependency on one DMA/IOVM segment while leaving Apple's DMA isolation fully intact. (Tadaaaa - sounds like a great Jackpot) Although iSCSI is our immediate use case, the same capability would also support software-defined block storage, NBD, cloud-backed disks, encryption, compression, deduplication and virtual test controllers. Scope Assessment We obviously do not know whether the internal framework is structured exactly as the pseudocode suggests, or whether the existing UserGetDataBuffer() backing buffer currently depends on part of the DMA-preparation path. We also do not expect the API names above to be adopted literally. The architectural change nevertheless appears local and additive: one opt-in controller capability one task-admission distinction one request-buffer lifetime guarantee If this approach proves successful for SCSIControllerDriverKit, a similar concept could likely be applied to BlockStorageDeviceDriverKit as well though from our perspective, that would require a significantly larger effort. With DriverKit 27 currently in beta, we thought this might be a useful time to raise the idea & it's the easiest solution. ;) Best regards, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26
Reply to BlockStorageDeviceDriverKit grant confirmed by support but shows "No Requests" in the portal. How to resolve?
Hi Kevin, thanks again for the detailed explanation and for requesting the SCSIControllerDriverKit entitlement. Your last reply makes the architectural situation much clearer. In particular, the point that neither BlockStorageDeviceDriverKit nor SCSIControllerDriverKit was originally designed with software-backed I/O in mind explains very well why we keep ending up at the same boundary from two different directions. I’m now trying to make a practical product decision and would appreciate your technical read. For a pure software iSCSI initiator, BlockStorageDeviceDriverKit still seems like the conceptually natural fit: there is no PCI/Thunderbolt device and no hardware DMA target. The block-device model itself already gives us what we need. The missing piece is a request-scoped, CPU-accessible memory descriptor for DoAsyncReadWrite instead of only dmaAddr. From the outside, this looks like a relatively contained addition to the existing BlockStorage I/O path, particularly since the underlying I/O path already has the memory descriptor needed to establish the DMA mapping. I fully appreciate that the implementation, ABI, security and validation work on your side may make it considerably less trivial than it appears from the API boundary. SCSIControllerDriverKit works functionally, which is an important result, but the current page-bound path is not viable for our product. Even if that limitation is fixed first, SCSI remains an adaptation of a hardware-controller interface to a software-backed device rather than the natural abstraction for it. So we are effectively choosing between three paths: • continue investing in SCSI and wait for the larger-I/O fix you mentioned; • wait for a proper CPU-accessible BlockStorage I/O path; • ship a classic KEXT, which on Apple Silicon means requiring Reduced Security. The third option is the one I would most like to avoid. Apart from the security implications, asking users to lower their Mac’s security policy simply to access an iSCSI LUN is a poor installation experience. At the same time, we need to make a realistic engineering decision. If there is a realistic prospect of BlockStorage gaining such a path in a future macOS release, investing significant effort into a new KEXT may make very little sense. I understand that you cannot provide a roadmap or promise a timeframe; even a qualitative indication of whether this is something worth designing around would be extremely helpful. What I’m really asking is for your technical intuition: given what you now know about this use case and the current implementation, is BlockStorage support for software-backed I/O something you could realistically see being addressed in a future macOS release, or should we plan on the assumption that SCSI is the only DriverKit path for the foreseeable future, with a KEXT as the fallback if its performance limitations cannot be resolved? If additional measurements, a minimal reproducer or anything else attached to FB23814013 would help make the case internally, I’m very happy to provide it. Thanks again, Torsten
Topic: Code Signing SubTopic: Entitlements Tags:
Replies
Boosts
Views
Activity
Aug ’26