From acc35d774ff361daaa1447e02c189cec14185b57 Mon Sep 17 00:00:00 2001 From: Lephe Date: Wed, 28 Apr 2021 17:53:19 +0200 Subject: [PATCH] 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. --- include/gint/dma.h | 56 +++++++++++++++++++++--------------- src/dma/dma.c | 69 ++++++++++++++++++++++++++++++++++----------- src/dma/inth.s | 33 ++++------------------ src/dma/memcpy.c | 4 +-- src/dma/memset.c | 3 +- src/r61524/r61524.c | 20 ++++++------- 6 files changed, 103 insertions(+), 82 deletions(-) diff --git a/include/gint/dma.h b/include/gint/dma.h index a771a67..fb499e1 100644 --- a/include/gint/dma.h +++ b/include/gint/dma.h @@ -6,6 +6,7 @@ #define GINT_DMA #include +#include /* 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 //--- diff --git a/src/dma/dma.c b/src/dma/dma.c index 5b72133..2cdc59f 100644 --- a/src/dma/dma.c +++ b/src/dma/dma.c @@ -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 */ diff --git a/src/dma/inth.s b/src/dma/inth.s index 53a4ffb..e438a70 100644 --- a/src/dma/inth.s +++ b/src/dma/inth.s @@ -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 */ diff --git a/src/dma/memcpy.c b/src/dma/memcpy.c index 9a86a4b..5d8b25c 100644 --- a/src/dma/memcpy.c +++ b/src/dma/memcpy.c @@ -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; } diff --git a/src/dma/memset.c b/src/dma/memset.c index b2f7016..74a182f 100644 --- a/src/dma/memset.c +++ b/src/dma/memset.c @@ -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; } diff --git a/src/r61524/r61524.c b/src/r61524/r61524.c index 1ba7a2f..466112b 100644 --- a/src/r61524/r61524.c +++ b/src/r61524/r61524.c @@ -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); } //---