AudioDriverKit: IOService::Stop ordering and IOOperationHandler callback drainage

I'm developing an iPadOS USB audio-MIDI driver for a hardware device. The audio side uses AudioDriverKit and USBDriverKit (DriverKit SDK 27.0, AudioDriverKit deployment target 21.0, iPadOS 18.0 minimum).

I need clarification about the ordering between IOUserAudioDriver::Stop, StopDevice/StopIO, and in-flight IOOperationHandler callbacks.

According to Apple's DriverKit documentation, IOService::Stop must quiesce outstanding operations before invoking its superclass implementation. AudioDriverKit documents host-controlled audio I/O and the StopDevice → device StopIO sequence.

However, I cannot find an explicit guarantee that the framework has stopped and drained all BeginRead/WriteEnd callbacks before invoking the driver's service Stop.

Relevant implementation (simplified):

// Audio driver service termination
kern_return_t result = StopUSBTransport();

if (result != kIOReturnSuccess) {
    return result;
}

// Release resources and close provider interfaces.
return Stop(in_provider, SUPERDISPATCH);

The transport retirement path currently refuses to release resources while framework I/O is marked active:

if (__atomic_load_n(&ivars->m_adk_running,
                    __ATOMIC_ACQUIRE)) {
    // Retain ownership; resources cannot be released safely.
    return kIOReturnBusy;
}

The local running state is cleared following successful device superclass StopIO, but this does not independently establish that already-entered framework audio callbacks have returned.

The USB teardown separately prevents new submissions, aborts outstanding transfers, waits for cancellation acknowledgements and native callback completion, and retains resources if ownership cannot be proven safe.

An offline regression exercising service termination while framework I/O is marked active reproduces the Busy result. We have not established that AudioDriverKit delivers this ordering on physical hardware.

Questions:

  1. Before invoking IOUserAudioDriver::Stop, does AudioDriverKit guarantee that StopDevice/StopIO has completed and that all outstanding IOOperationHandler invocations have returned?

  2. If not, what supported mechanism can a driver invoke during service Stop to prevent new BeginRead/WriteEnd callbacks and wait for already-entered callbacks to finish?

  3. Does calling superclass IOUserAudioDriver::Stop establish this callback-drain boundary? If so, how should hardware and USB resources be shut down when provider access is no longer permitted afterward?

  4. If termination cannot safely complete, is returning kIOReturnBusy a supported way to defer service termination? Is a subsequent retry guaranteed?

I'm looking for the documented lifecycle and synchronization contract, rather than a workaround based on assumed callback ordering.

Any clarification from Apple's DriverKit or Core Audio engineering team would be appreciated.

According to Apple's DriverKit documentation, IOService::Stop must quiesce outstanding operations.

Yes. Keep in mind that this is the base class (IOService) method, so "IOService::Stop" means "my driver has finished its work and is no longer operational.” By definition, that means any family-specific/higher-level work must be finished before this point.

However, the most critical detail is here:

before invoking its superclass implementation.

The "natural" C++ pattern is that you call a method, that method does its work, and then that method calls super. That's NOT (necessarily) how your class's implementation needs to work. In many cases, what your implementation will be doing is actually:

  1. System calls DEXT::Stop().

  2. Your DEXT returns from Stop() and finishes its own cleanup work.

  3. Your DEXT finishes that work and calls super::Stop().

Shifting to your specific questions:

Before invoking IOUserAudioDriver::Stop, does AudioDriverKit guarantee that StopDevice/StopIO has completed and that all outstanding IOOperationHandler invocations have returned?

I haven't looked closely, but the answer is almost certainly no. Your DEXT is the "bridge" between system (for example, USB and Audio), so the system can't really determine what "should" happen when a given driver is destroyed. For example, a driver that aggregates multiple hardware devices into a single audio interface might end up having Stop called all the time without ever passing that on to a different driver.

If not, what supported mechanism can a driver invoke during service Stop to prevent new BeginRead/WriteEnd callbacks and wait for already-entered callbacks to finish?

Does calling superclass IOUserAudioDriver::Stop establish this callback-drain boundary? If so, how should hardware and USB resources be shut down when provider access is no longer permitted afterward?

Until you call super::Stop(), IOKit level "stop" hasn't actually started. That means your DEXT can continue performing I/O and otherwise operating "normally"[1].

If termination cannot safely complete, is returning kIOReturnBusy a supported way to defer service termination?

Funny story there... You can return whatever you want, because I don't think anyone is really looking at what you return. The starting point for DriverKit Stop is IOService::Stop_async_Impl which, as you can see in its base implementation here, returns void, meaning the kernel itself is discarding the error code. Looking through our own implementation, I haven't actually found ANY driver that uses that error code for anything interesting, so I think the error code was included in case one of the families decided to use it, not because it had any immediate usefulness. I'd recommend returning kIOReturnSuccess, but that's only because I've seen a few drivers do assert(ret==kIOReturnSuccess), not because I think it'll actually change anything.

Is a subsequent retry guaranteed?

To be clear, there's no retry because there doesn't need to be. You tell the system when you're "done" by calling super::Stop().

[1] The fact that you can submit I/O to your provider doesn't mean your provider has to perform that I/O. When a device is unplugged, you’re allowed to submit your I/O to your provider, and your provider is free to fail that I/O until you call super::Stop.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

AudioDriverKit: IOService::Stop ordering and IOOperationHandler callback drainage
 
 
Q