Updating pending isochronous OUT buffers

I'm developing a low-latency USB audio-MIDI driver using AudioDriverKit and USBDriverKit for macOS and iPadOS.

I've observed occasional delays in USB completion callbacks. The manufacturer's official macOS driver uses IOUSBHost to queue longer isochronous transfers and update their mapped OUT buffers as audio samples become available.

I've reviewed the relevant IOKit and IOUSBHost documentation, but haven't established whether USBDriverKit supports an equivalent approach.

Does USBDriverKit permit modifying portions of a CreateIOBuffer OUT buffer while an IsochIO request is still pending, specifically to supply audio for future USB frames?

If so, what synchronization requirements and controller-prefetch guarantees determine the latest safe time to update those regions?

If this isn't supported, is there a recommended alternative for maintaining low-latency isochronous audio despite delayed completion callbacks?

Thanks.

Answered by DTS Engineer in 912490022

I’m developing a low-latency USB audio-MIDI driver using AudioDriverKit and USBDriverKit for macOS and iPadOS.

I've observed occasional delays in USB completion callbacks. The manufacturer's official macOS driver uses IOUSBHost to queue longer isochronous transfers and update their mapped OUT buffers as audio samples become available.

I've reviewed the relevant IOKit and IOUSBHost documentation, but haven't established whether USBDriverKit supports an equivalent approach.

Basically, "yes". More specifically, USBDriverKit and the IOUSBHost framework are actually quite similar, in that what both actually "do” is control a USB device by sending commands to the in-kernel USB driver which ACTUALLY controls the device. The main difference/benefit DriverKit provides is the ability to inject services back INTO the kernel, not in the mechanics of how it actually interacts with its provider.

Does USBDriverKit permit modifying portions of a CreateIOBuffer OUT buffer while an IsochIO request is still pending, specifically to supply audio for future USB frames?

Yes, as this is basically the entire "point" of our Isochronous APIs. More specifically, the basic idea here is that what's actually slow/high latency here is:

  • Setting up the chain of commands that you're doing to send.

  • Receiving the completion for any given event.

...while the actual transmission of any given packet happens at interrupt time, basically make it as fast as it's possible to be.

SO, the idea here is that you transfer data by:

  1. Scheduling a long series of transfers to occur at specific times "in the future".

  2. Backfilling the data into each of those transfers "before" they're actually sent.

  3. ...then looping back to #1 by scheduling more transfers before #1 finishes, allowing the transfer process to continue indefinitely.

If so, what synchronization requirements and controller-prefetch guarantees determine the latest safe time to update those regions?

I'm not sure what you mean here. The memory you're writing to has already been wired, so it can be modified any time "before" the actual transfer starts. Roughly when that transfer starts can be determined mathematically, but part of the "point" of this architecture is to avoid explicit synchronization between the data producer and the sending driver.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Accepted Answer

I’m developing a low-latency USB audio-MIDI driver using AudioDriverKit and USBDriverKit for macOS and iPadOS.

I've observed occasional delays in USB completion callbacks. The manufacturer's official macOS driver uses IOUSBHost to queue longer isochronous transfers and update their mapped OUT buffers as audio samples become available.

I've reviewed the relevant IOKit and IOUSBHost documentation, but haven't established whether USBDriverKit supports an equivalent approach.

Basically, "yes". More specifically, USBDriverKit and the IOUSBHost framework are actually quite similar, in that what both actually "do” is control a USB device by sending commands to the in-kernel USB driver which ACTUALLY controls the device. The main difference/benefit DriverKit provides is the ability to inject services back INTO the kernel, not in the mechanics of how it actually interacts with its provider.

Does USBDriverKit permit modifying portions of a CreateIOBuffer OUT buffer while an IsochIO request is still pending, specifically to supply audio for future USB frames?

Yes, as this is basically the entire "point" of our Isochronous APIs. More specifically, the basic idea here is that what's actually slow/high latency here is:

  • Setting up the chain of commands that you're doing to send.

  • Receiving the completion for any given event.

...while the actual transmission of any given packet happens at interrupt time, basically make it as fast as it's possible to be.

SO, the idea here is that you transfer data by:

  1. Scheduling a long series of transfers to occur at specific times "in the future".

  2. Backfilling the data into each of those transfers "before" they're actually sent.

  3. ...then looping back to #1 by scheduling more transfers before #1 finishes, allowing the transfer process to continue indefinitely.

If so, what synchronization requirements and controller-prefetch guarantees determine the latest safe time to update those regions?

I'm not sure what you mean here. The memory you're writing to has already been wired, so it can be modified any time "before" the actual transfer starts. Roughly when that transfer starts can be determined mathematically, but part of the "point" of this architecture is to avoid explicit synchronization between the data producer and the sending driver.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks, Kevin. Your explanation has helped clarify the intended architecture.

To explain my earlier synchronization question: I'm using 32 ms isochronous OUT requests containing multiple scheduled microframes, with audio data written into the mapped buffers as it becomes available.

I was trying to establish whether the payload for a later microframe can still be updated after the request has begun transmitting, provided that particular microframe hasn't yet been sent.

In other words, does the safe-update boundary apply to the entire request or to each scheduled microframe?

I'm asking because this distinction affects how closely to transmission I can safely supply audio without increasing latency.

Thanks again,
Luke

Thanks, Kevin. Your explanation has helped clarify the intended architecture.

To explain my earlier synchronization question: I'm using 32 ms isochronous OUT requests containing multiple scheduled microframes, with audio data written into the mapped buffers as it becomes available.

Just to clarify, did you really mean "32 ms" (milliseconds)? If so, then I think the problem here is that your message count is WAY too small. Frankly, I'd expect our bulk transfer pipes to be able to do 30 transfers per second without much difficulty. For reference, TN3190 has specific rate guidance for particular transfer types, and synchronous data endpoints use this example:

"For example, if a device is using a sample rate of 48 kHz, it should transfer exactly 48 audio samples every millisecond as defined by the SOF tokens on the bus. If it is on a High Speed bus with a polling interval of 125 µs, it should transfer exactly 6 samples every 125 µs."

That translates to 8000 transfers per second. That's the kind of update frequency isochronous was designed to support.

I was trying to establish whether the payload for a later microframe can still be updated after the request has begun transmitting, provided that particular microframe hasn't yet been sent.

In other words, does the safe-update boundary apply to the entire request or to each scheduled microframe?

This turns into a philosophical question about what the word "transmit" means. The controller "owns" the DMA buffer from the moment the transfer “starts," and how exactly it manipulates it isn't really defined. My guess is that we packetize the entire buffer in a single pass, which means there isn't really any "window" for you to use. However, the real problem here is that the actual behavior is going to depend on the specifics of the controller implementation, which means there isn't anything you can really rely on here.

I’m asking because this distinction affects how closely to transmission I can safely supply audio without increasing latency.

So, this makes me think you are thinking in terms of milliseconds (ms) not microseconds (µs). Audio latency is typically measured in milliseconds, with "OK" being “10–20 ms" and "Great!" being “3–6 ms". The problem here is that 3 ms (the VERY best) is 3000 µs. In other words, at the BEST latency level, you're talking about 24 transmissions spaced every 125 µs. In other words, the audio system generally fills up multiple USB requests per callback because the USB system is firing transfers more frequently than the real-time thread is firing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

For context, the device that I'm building the driver for is a BOSS SY-1000 Guitar Synthesizer, with eight channels of 32-bit audio input and eight channels of output at 48 kHz, alongside two MIDI ports.

Yes, I meant 32 ms requests, each containing 256 high-speed microframes at 125 µs intervals. So the actual USB packet cadence remains 8,000 per second, rather than approximately 31 transfers per second.

Thanks for your clarification about controller ownership. I had been treating the individual scheduled microframes within a pending request as independently writable, but I now understand that the controller's handling of the entire DMA buffer after the request starts isn't guaranteed.

I've consequently revised the implementation to use shorter, 1 ms isochronous OUT requests, with each payload finalized before submission and left unchanged until completion and safe retirement.

Initial physical testing confirms that this arrangement is functioning, although I'm still working through some unrelated input-continuity problems.

Thanks again for taking the time to explain all this. Your insights have been invaluable to this newb!

Regards,
Luke