dma: add an asynchronous API

This change adds asynchronous capabilities to the DMA API. Previously,
transfers would start asynchronously but could only be completed by a
call to dma_transfer_wait(). The API now supports a callback, as well
as the dma_transfer_sync() variant, to be consistent with the upcoming
USB API that has both _sync and _async versions of functions.

The interrupt handler of the DMA was changed to include a return to
userland, which is required to perform the callback.

* dma_transfer() is now an obsolete synonym for dma_transfer_async()
  with no callback.
* dma_transfer_noint() is now a synonym for dma_transfer_atomic(), for
  consistency with the upcoming USB API.
This commit is contained in:
Lephe 2021-04-28 17:53:19 +02:00
parent 42081c9968
commit acc35d774f
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
6 changed files with 103 additions and 82 deletions

View File

@ -6,6 +6,7 @@
#define GINT_DMA
#include <gint/defs/types.h>
#include <gint/defs/call.h>
/* dma_size_t - Transfer block size */
typedef enum
@ -38,11 +39,12 @@ typedef enum
} dma_address_t;
/* dma_transfer() - Start a data transfer on channel 0
This function returns just when the transfer starts. The transfer will end
later on and the DMA will be stopped by an interrupt. Call
dma_transfer_wait() if you need to wait for the transfer to finish. Don't
start a new transfer until the current one is finished!
/* dma_transfer_async(): Perform an asynchronous DMA data transfer
This function starts a DMA data transfer and returns immediately. The
provided callback will be invoked once the transfer is finish. You can also
call dma_transfer_wait() to wait until the transfer completes. You can
create a callback with GINT_CALL() or pass GINT_CALL_NULL.
@channel DMA channel (0..5)
@size Transfer size
@ -50,32 +52,40 @@ typedef enum
@src Source pointer, must be aligned with transfer size
@src_mode Source address mode
@dst Destination address, must be aligned with transfer size
@dst_mode Destination address mode */
void dma_transfer(int channel, dma_size_t size, uint length,
void const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_mode);
/* dma_transfer_wait() - Wait for a transfer to finish
You should call this function when you need to transfer to be complete
before continuing execution. If you are sure that the transfer is finished,
this is not necessary (the only way to know is to look at the DMA registers
or record interrupts).
@dst_mode Destination address mode
@callback Function to invoke when the transfer finishes
-> Returns true on success. */
bool dma_transfer_async(int channel, dma_size_t size, uint length,
void const *src, dma_address_t src_mode, void *dst,
dma_address_t dst_mode, gint_call_t callback);
/* dma_transfer_wait(): Wait for an asynchronous transfer to finish
@channel DMA channel (0..5) */
void dma_transfer_wait(int channel);
/* dma_transfer_noint() - Perform a data transfer without interrupts
This function performs a transfer much like dma_transfer(), but doesn't use
interrupts and *actively waits* for the transfer to finish, returning when
it's finished. Don't call dma_transfer_wait() after using this function.
/* dma_transfer_sync(): Perform an synchronous DMA data transfer
Like dma_transfer_async(), but only returns once the transfer completes. */
bool dma_transfer_sync(int channel, dma_size_t size, uint length,
void const *src, dma_address_t src_mode, void *dst,
dma_address_t dst_mode);
Not using interrupts is a bad design idea for a majority of programs, and is
only ever needed to display panic messages inside exception handlers. */
void dma_transfer_noint(int channel, dma_size_t size, uint blocks,
/* dma_transfer_atomic(): Perform a data transfer without interrupts
This function performs a transfer much like dma_transfer_sync(), but doesn't
use interrupts and actively waits for the transfer to finish. Not using
interrupts is a bad design idea for a majority of programs, and is only ever
needed to display panic messages inside exception handlers. */
void dma_transfer_atomic(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_mode);
/* Deprecated version of dma_transfer_async() that did not have a callback */
__attribute__((deprecated("Use dma_transfer_async() instead")))
void dma_transfer(int channel, dma_size_t size, uint length, void const *src,
dma_address_t src_mode, void *dst, dma_address_t dst_mode);
/* Old name for dma_transfer_atomic() */
#define dma_transfer_noint dma_transfer_atomic
//---
// DMA-based memory manipulation functions
//---

View File

@ -14,6 +14,9 @@
typedef volatile sh7305_dma_channel_t channel_t;
/* Callbacks for all channels */
static gint_call_t dma_callbacks[6] = { 0 };
/* dma_channel(): Get address of a DMA channel */
static channel_t *dma_channel(int channel)
{
@ -60,8 +63,8 @@ static uint32_t dma_translate(void const *address)
//---
/* dma_setup(): Setup the DMA in interrupt or no-interrupt mode.
The first parameters are as for dma_transfer() and dma_transfer_noint(). The
last parameter indicates whether interrupts should be used.
The first parameters are as for dma_transfer() and dma_transfer_atomic().
The last parameter indicates whether interrupts should be used.
Returns non-zero if the DMA is busy or a configuration error occurs. */
static int dma_setup(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode,
@ -103,17 +106,36 @@ static int dma_setup(int channel, dma_size_t size, uint blocks,
return 0;
}
/* dma_transfer(): Perform a data transfer */
void dma_transfer(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_mode)
bool dma_transfer_async(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode, void *dst,
dma_address_t dst_mode, gint_call_t callback)
{
if(dma_setup(channel, size, blocks, src, src_mode, dst, dst_mode, 1))
return;
return false;
dma_callbacks[channel] = callback;
/* Enable channel, starting the DMA transfer. */
channel_t *ch = dma_channel(channel);
ch->CHCR.DE = 1;
return true;
}
/* Interrupt handler for all finished DMA transfers */
static void dma_interrupt_transfer_ended(int channel)
{
channel_t *ch = dma_channel(channel);
ch->CHCR.DE = 0;
ch->CHCR.TE = 0;
DMA.OR.AE = 0;
DMA.OR.NMIF = 0;
if(dma_callbacks[channel].function)
{
gint_call(dma_callbacks[channel]);
dma_callbacks[channel] = GINT_CALL_NULL;
}
}
/* dma_transfer_wait(): Wait for a transfer to finish */
@ -144,8 +166,18 @@ void dma_transfer_wait(int channel)
}
}
/* dma_transfer_noint(): Perform a data transfer without interruptions */
void dma_transfer_noint(int channel, dma_size_t size, uint blocks,
bool dma_transfer_sync(int channel, dma_size_t size, uint length,
void const *src, dma_address_t src_mode, void *dst,
dma_address_t dst_mode)
{
if(!dma_transfer_async(channel, size, length, src, src_mode, dst,
dst_mode, GINT_CALL_NULL)) return false;
dma_transfer_wait(channel);
return true;
}
/* dma_transfer_atomic(): Perform a data transfer without interruptions */
void dma_transfer_atomic(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_mode)
{
@ -169,6 +201,14 @@ void dma_transfer_noint(int channel, dma_size_t size, uint blocks,
DMA.OR.NMIF = 0;
}
/* Deprecated version of dma_transfer_async() that did not have a callback */
void dma_transfer(int channel, dma_size_t size, uint length, void const *src,
dma_address_t src_mode, void *dst, dma_address_t dst_mode)
{
dma_transfer_async(channel, size, length, src, src_mode, dst, dst_mode,
GINT_CALL_NULL);
}
//---
// Initialization
//---
@ -183,14 +223,11 @@ static void configure(void)
for(int i = 0; i < 6; i++)
{
/* Install interrupt handler */
void *h = intc_handler(codes[i], inth_dma_te, 32);
channel_t *ch = dma_channel(i);
intc_handler_function(codes[i],
GINT_CALL(dma_interrupt_transfer_ended, i));
/* Set its CHCR address */
*(volatile uint32_t **)(h + 24) = &ch->CHCR.lword;
/* Clear the enable flag */
ch->CHCR.DE = 0;
/* Disable the channel */
dma_channel(i)->CHCR.DE = 0;
}
/* Install the address error gate */

View File

@ -1,37 +1,14 @@
/*
** gint:dma:inth - Interrupt handler for the DMA
** An easy one, just clears some flags and marks all transfers as finished.
** gint:dma:inth - DMA address error handler
** A particular handler that jumps into a panic.
*/
.global _inth_dma_te
.global _inth_dma_ae
.global _inth_dma_ae /* 32 bytes */
.section .gint.blocks, "ax"
.align 4
/* DMA TRANSFER ENDED INTERRUPT HANDLER - 32 BYTES */
_inth_dma_te:
/* Clear the TE flag and DMA Enable in CHCR */
mov.l 1f, r1
mov.l @r1, r0
mov #-4, r2
and r2, r0
mov.l r0, @r1
/* Clear the AE and NMIF flags in OR */
mov.l 2f, r1
mov.w @r1, r0
mov #-7, r2
and r2, r0
mov.w r0, @r1
rts
nop
1: .long 0 /* CHCR, set dynamically */
2: .long 0xfe008060 /* DMA.OR */
/* DMA ADDRESS ERROR INTERRUPT HANDLER - 18 BYTES */
/* DMA ADDRESS ERROR INTERRUPT HANDLER - 22 BYTES */
_inth_dma_ae:
/* Manually RTE into the panic routine, preserving SPC */

View File

@ -4,8 +4,6 @@
void *dma_memcpy(void * __restrict dst, const void * __restrict src,
size_t size)
{
dma_transfer(1, DMA_32B, size >> 5, src, DMA_INC, dst, DMA_INC);
dma_transfer_wait(1);
dma_transfer_sync(1, DMA_32B, size >> 5, src, DMA_INC, dst, DMA_INC);
return dst;
}

View File

@ -14,7 +14,6 @@ void *dma_memset(void *dst, uint32_t l, size_t size)
different memory regions, making the DMA faster than the CPU. */
for(int i = 0; i < 8; i++) ILbuf[i] = l;
dma_transfer(1, DMA_32B, size >> 5, ILbuf, DMA_FIXED, dst, DMA_INC);
dma_transfer_wait(1);
dma_transfer_sync(1, DMA_32B, size>>5, ILbuf, DMA_FIXED, dst, DMA_INC);
return dst;
}

View File

@ -150,18 +150,18 @@ void r61524_display(uint16_t *vram, int start, int height, int method)
appear. */
int blocks = 99 * (height >> 2);
/* Now roll! */
if(method == R61524_DMA)
{
/* If the previous transfer is still running, wait for it */
if(method == R61524_DMA) {
/* If the previous transfer is still running, wait for it; then
start sending asynchronously and return. */
dma_transfer_wait(0);
/* Usa a normal, interrupt-based transfer */
dma_transfer(0, DMA_32B, blocks, src, DMA_INC, dst, DMA_FIXED);
/* Function returns early so that rendering can continue on
another VRAM while the transfer is still being done */
dma_transfer_async(0, DMA_32B, blocks, src, DMA_INC, dst,
DMA_FIXED, GINT_CALL_NULL);
}
else {
/* Transfer atomically */
dma_transfer_atomic(0, DMA_32B, blocks, src, DMA_INC, dst,
DMA_FIXED);
}
else dma_transfer_noint(0, DMA_32B, blocks, src,DMA_INC,dst,DMA_FIXED);
}
//---