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
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
6 changed files with 103 additions and 82 deletions

View file

@ -6,6 +6,7 @@
#define GINT_DMA #define GINT_DMA
#include <gint/defs/types.h> #include <gint/defs/types.h>
#include <gint/defs/call.h>
/* dma_size_t - Transfer block size */ /* dma_size_t - Transfer block size */
typedef enum typedef enum
@ -38,11 +39,12 @@ typedef enum
} dma_address_t; } dma_address_t;
/* dma_transfer() - Start a data transfer on channel 0 /* dma_transfer_async(): Perform an asynchronous DMA data transfer
This function returns just when the transfer starts. The transfer will end
later on and the DMA will be stopped by an interrupt. Call This function starts a DMA data transfer and returns immediately. The
dma_transfer_wait() if you need to wait for the transfer to finish. Don't provided callback will be invoked once the transfer is finish. You can also
start a new transfer until the current one is finished! 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) @channel DMA channel (0..5)
@size Transfer size @size Transfer size
@ -50,32 +52,40 @@ typedef enum
@src Source pointer, must be aligned with transfer size @src Source pointer, must be aligned with transfer size
@src_mode Source address mode @src_mode Source address mode
@dst Destination address, must be aligned with transfer size @dst Destination address, must be aligned with transfer size
@dst_mode Destination address mode */ @dst_mode Destination address mode
void dma_transfer(int channel, dma_size_t size, uint length, @callback Function to invoke when the transfer finishes
void const *src, dma_address_t src_mode, -> Returns true on success. */
void *dst, dma_address_t dst_mode); bool dma_transfer_async(int channel, dma_size_t size, uint length,
void const *src, dma_address_t src_mode, void *dst,
/* dma_transfer_wait() - Wait for a transfer to finish dma_address_t dst_mode, gint_call_t callback);
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).
/* dma_transfer_wait(): Wait for an asynchronous transfer to finish
@channel DMA channel (0..5) */ @channel DMA channel (0..5) */
void dma_transfer_wait(int channel); void dma_transfer_wait(int channel);
/* dma_transfer_noint() - Perform a data transfer without interrupts /* dma_transfer_sync(): Perform an synchronous DMA data transfer
This function performs a transfer much like dma_transfer(), but doesn't use Like dma_transfer_async(), but only returns once the transfer completes. */
interrupts and *actively waits* for the transfer to finish, returning when bool dma_transfer_sync(int channel, dma_size_t size, uint length,
it's finished. Don't call dma_transfer_wait() after using this function. 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 /* dma_transfer_atomic(): Perform a data transfer without interrupts
only ever needed to display panic messages inside exception handlers. */
void dma_transfer_noint(int channel, dma_size_t size, uint blocks, 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 const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_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 // DMA-based memory manipulation functions
//--- //---

View file

@ -14,6 +14,9 @@
typedef volatile sh7305_dma_channel_t channel_t; 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 */ /* dma_channel(): Get address of a DMA channel */
static channel_t *dma_channel(int 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. /* dma_setup(): Setup the DMA in interrupt or no-interrupt mode.
The first parameters are as for dma_transfer() and dma_transfer_noint(). The The first parameters are as for dma_transfer() and dma_transfer_atomic().
last parameter indicates whether interrupts should be used. The last parameter indicates whether interrupts should be used.
Returns non-zero if the DMA is busy or a configuration error occurs. */ 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, static int dma_setup(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode, 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; return 0;
} }
/* dma_transfer(): Perform a data transfer */ bool dma_transfer_async(int channel, dma_size_t size, uint blocks,
void dma_transfer(int channel, dma_size_t size, uint blocks, void const *src, dma_address_t src_mode, void *dst,
void const *src, dma_address_t src_mode, dma_address_t dst_mode, gint_call_t callback)
void *dst, dma_address_t dst_mode)
{ {
if(dma_setup(channel, size, blocks, src, src_mode, dst, dst_mode, 1)) 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. */ /* Enable channel, starting the DMA transfer. */
channel_t *ch = dma_channel(channel); channel_t *ch = dma_channel(channel);
ch->CHCR.DE = 1; 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 */ /* 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 */ bool dma_transfer_sync(int channel, dma_size_t size, uint length,
void dma_transfer_noint(int channel, dma_size_t size, uint blocks, 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 const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_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; 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 // Initialization
//--- //---
@ -183,14 +223,11 @@ static void configure(void)
for(int i = 0; i < 6; i++) for(int i = 0; i < 6; i++)
{ {
/* Install interrupt handler */ intc_handler_function(codes[i],
void *h = intc_handler(codes[i], inth_dma_te, 32); GINT_CALL(dma_interrupt_transfer_ended, i));
channel_t *ch = dma_channel(i);
/* Set its CHCR address */ /* Disable the channel */
*(volatile uint32_t **)(h + 24) = &ch->CHCR.lword; dma_channel(i)->CHCR.DE = 0;
/* Clear the enable flag */
ch->CHCR.DE = 0;
} }
/* Install the address error gate */ /* Install the address error gate */

View file

@ -1,37 +1,14 @@
/* /*
** gint:dma:inth - Interrupt handler for the DMA ** gint:dma:inth - DMA address error handler
** An easy one, just clears some flags and marks all transfers as finished. ** A particular handler that jumps into a panic.
*/ */
.global _inth_dma_te .global _inth_dma_ae /* 32 bytes */
.global _inth_dma_ae
.section .gint.blocks, "ax" .section .gint.blocks, "ax"
.align 4 .align 4
/* DMA TRANSFER ENDED INTERRUPT HANDLER - 32 BYTES */ /* DMA ADDRESS ERROR INTERRUPT HANDLER - 22 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 */
_inth_dma_ae: _inth_dma_ae:
/* Manually RTE into the panic routine, preserving SPC */ /* Manually RTE into the panic routine, preserving SPC */

View file

@ -4,8 +4,6 @@
void *dma_memcpy(void * __restrict dst, const void * __restrict src, void *dma_memcpy(void * __restrict dst, const void * __restrict src,
size_t size) size_t size)
{ {
dma_transfer(1, DMA_32B, size >> 5, src, DMA_INC, dst, DMA_INC); dma_transfer_sync(1, DMA_32B, size >> 5, src, DMA_INC, dst, DMA_INC);
dma_transfer_wait(1);
return dst; 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. */ different memory regions, making the DMA faster than the CPU. */
for(int i = 0; i < 8; i++) ILbuf[i] = l; 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_sync(1, DMA_32B, size>>5, ILbuf, DMA_FIXED, dst, DMA_INC);
dma_transfer_wait(1);
return dst; return dst;
} }

View file

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