"Audio" device with ping pong buffers best approach.

We have some pretty old PCIe hardware that runs a Linux driver and my boss has asked me to look into Writing a windows driver for it.

Thus far I have mapped the BAR registers and and managed to flash an LED using a timer interrupt.

Now for the hard part.

I think it is best to give an overview about how the current hardware works rather than describing the Linux drivers. It has DMA read and write. Reading from eight audio ADC codecs and writing to to two DAC codecs.

The device has two sets of buffers for read and write (no scatter gather) to be operated in ping pong mode. The device effectively operates continuously. So when it interrupts one set of ADC codec DMA is read and a set of DAC codec to be DMA’d is written on one set of ping pong buffers. When the device interrupts again it does the same on the other set of ping pong buffers.

This indicates my problem. I have a bit of an understanding how DMA works with WDF (I am hoping to use common buffers) however the DMA read and write occur on the same interrupt.

From my understanding the requests for read() and write() are completed separately on separate iterations of the ISR.

Our user application reads the ADC data, processes it then uses this processed data to write the DAC data.

Could I just have a read DMA transaction and simply write data directly to the common buffer for the write transaction?

rob18767 wrote:

I think it is best to give an overview about how the current hardware works rather than describing the Linux drivers. It has DMA read and write. Reading from eight audio ADC codecs and writing to to two DAC codecs.

The device has two sets of buffers for read and write (no scatter gather) to be operated in ping pong mode. The device effectively operates continuously. So when it interrupts one set of ADC codec DMA is read and a set of DAC codec to be DMA’d is written on one set of ping pong buffers. When the device interrupts again it does the same on the other set of ping pong buffers.

This indicates my problem. I have a bit of an understanding how DMA works with WDF (I am hoping to use common buffers) however the DMA read and write occur on the same interrupt.

From my understanding the requests for read() and write() are completed separately on separate iterations of the ISR.

Our user application reads the ADC data, processes it then uses this processed data to write the DAC data.

Could I just have a read DMA transaction and simply write data directly to the common buffer for the write transaction?

The KMDF DMA abstraction is a good one, but it isn’t perfectly suited to
every situation.  At the risk of overgeneralizing, the WDFDMATRANSACTION
abstraction works really well for scatter/gather hardware where you’re
trying to use user buffers directly.  If you have a device that needs
common buffers, then there’s really no need to worry about using
transactions.  You will already have the physical addresses from the
common buffer, so just trigger your DMA the way we’ve always done it.

You will want to be careful about your data handling.  In general, your
interrupt handler should just acknowledge the interrupt and start up a
DPC to do the data handling.  You don’t want to be copying data or
completing requests in an ISR.

The KMDF DMA abstraction is a good one, but it isn’t perfectly suited to
every situation. At the risk of overgeneralizing, the WDFDMATRANSACTION
abstraction works really well for scatter/gather hardware where you’re
trying to use user buffers directly. If you have a device that needs
common buffers, then there’s really no need to worry about using
transactions. You will already have the physical addresses from the
common buffer, so just trigger your DMA the way we’ve always done it.

You will want to be careful about your data handling. In general, your
interrupt handler should just acknowledge the interrupt and start up a
DPC to do the data handling. You don’t want to be copying data or
completing requests in an ISR.

Thanks for your reply.

I am thinking more along the terms of setting up a work item to handle the data. I managed to create a write routine with the following code:

    //
// Get the DevExt from the Queue handle
//
devExt = evioGetDeviceContext(WdfIoQueueGetDevice(Queue));

//
// Get the request buffer and perform write operation here
//
status = WdfRequestRetrieveInputMemory(Request, &memory);
if (NT_SUCCESS(status)) {
	WdfMemoryCopyToBuffer(memory,0,devExt->DACBuffers[0].WriteCommonBufferBase,4096);
}

The addresses for the DMA write just need to be toggled on the same interrupt as the read. If we’re not ready in time then it is tough luck anyhow.

The Linux driver, as is, is completely unsuitable to base the Windows driver on.

That said I always thought that copy_from_buffer and copy_to_buffer were a cop out when it came to keeping user and kernel space separate.

That’s the basic principle. It’s important to remember that today’s CPUs copy data really, really fast. It is certainly possible to have zero-copy DMA, but only with sufficiently flexible DMA hardware (that is, scatter/gather and 64-bit addressing).