From 95dbec17ab2bde16199785bdc9db74490ccd114d Mon Sep 17 00:00:00 2001 From: Lephe Date: Wed, 5 May 2021 15:17:23 +0200 Subject: [PATCH] cpu: allow functions to block the sleep() function This change introduces new sleep_block() and sleep_unblock() functions that control whether the sleep() function actually sleeps. This type of behavior was already implemented in the DMA driver, since DMA access to on-chip memory is paused when sleeping (on-chip memory being paused itself), which would make waiting for a DMA transfer a freeze. Because DMA transfers are now asynchronous, and USB transfers that may involve on-chip memory are coming, this API change allows the DMA and USB drivers to block the sleep() function so that user code can sleep() for interrupts without having to worry about asynchronous tasks requiring on-chip memory to complete. --- CMakeLists.txt | 1 + include/gint/clock.h | 9 +++------ include/gint/cpu.h | 28 ++++++++++++++++++++++++++++ src/cpu/sleep.c | 22 ++++++++++++++++++++++ src/dma/dma.c | 26 +++++++++++++++++--------- 5 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 src/cpu/sleep.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b985ef3..fe98b79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ set(SOURCES_COMMON src/cpu/atomic.c src/cpu/cpu.c src/cpu/registers.s + src/cpu/sleep.c src/dma/dma.c src/dma/inth.s src/dma/memcpy.c diff --git a/include/gint/clock.h b/include/gint/clock.h index c6fc0c9..f02afc1 100644 --- a/include/gint/clock.h +++ b/include/gint/clock.h @@ -6,6 +6,9 @@ #define GINT_CLOCK #include +/* This header used to expose the sleep() function; include to + ensure this is still the case */ +#include //--- // Clock signals @@ -55,12 +58,6 @@ const clock_frequency_t *clock_freq(void); // Sleep functions //--- -/* sleep() - halt the processor until an event occurs - The function stops the processor until an interrupt is accepted; the - duration is not known in advance. This function should be used when the - add-in is idle, for instance while waiting for keyboard input. */ -#define sleep() __asm__("sleep") - /* sleep_us(): Sleep for a fixed duration in microseconds Stops the processor until the specified delay in microseconds has elapsed. (The processor will still wake up occasionally to handle interrupts.) This diff --git a/include/gint/cpu.h b/include/gint/cpu.h index 3c16b1c..ffad918 100644 --- a/include/gint/cpu.h +++ b/include/gint/cpu.h @@ -79,6 +79,34 @@ typedef lword_union(cpu_sr_t, cpu_sr_t cpu_getSR(void); void cpu_setSR(cpu_sr_t sr); +//--- +// Miscellaneous functions +//--- + +/* sleep(): Put the processor to sleep + + This function uses the [sleep] instruction to put the processor in sleep + mode. This allows reduced energy consumption while waiting for interrupts. + + In some certain situations, sleeping would block the process that is being + waited for (generally when on-chip memory is involved). When this occurs, + this function will not sleep at all and instead return instantly. + + The indented use for sleep() is *always* in some sort of loop, as there is + no guarantee about time or interrupts elapsed before this function + returns. */ +void sleep(void); + +/* sleep_block(): Block processor sleep + + This function blocks processor sleep until sleep_unblock() is called. The + driver maintains a counter of the number of blocking sources, therefore it + is important that *every* sleep_block() is followed by a sleep_unblock(). */ +void sleep_block(void); + +/* sleep_unblock(): Cancel a processor sleep block */ +void sleep_unblock(void); + //--- // Configuration //--- diff --git a/src/cpu/sleep.c b/src/cpu/sleep.c new file mode 100644 index 0000000..2e0fad8 --- /dev/null +++ b/src/cpu/sleep.c @@ -0,0 +1,22 @@ +#include + +volatile int cpu_sleep_block_counter = 0; + +void sleep(void) +{ + if(cpu_sleep_block_counter <= 0) __asm__("sleep"); +} + +void sleep_block(void) +{ + cpu_atomic_start(); + cpu_sleep_block_counter++; + cpu_atomic_end(); +} + +void sleep_unblock(void) +{ + cpu_atomic_start(); + cpu_sleep_block_counter--; + cpu_atomic_end(); +} diff --git a/src/dma/dma.c b/src/dma/dma.c index 2cdc59f..141b05b 100644 --- a/src/dma/dma.c +++ b/src/dma/dma.c @@ -16,6 +16,8 @@ typedef volatile sh7305_dma_channel_t channel_t; /* Callbacks for all channels */ static gint_call_t dma_callbacks[6] = { 0 }; +/* Sleep blocking flags for all channels */ +static bool dma_sleep_blocking[6] = { 0 }; /* dma_channel(): Get address of a DMA channel */ static channel_t *dma_channel(int channel) @@ -103,6 +105,14 @@ static int dma_setup(int channel, dma_size_t size, uint blocks, DMA.OR.AE = 0; DMA.OR.NMIF = 0; + /* Block sleep when the transfer involves on-chip memory */ + dma_sleep_blocking[channel] = false; + + if(ch->SAR >= 0xe5007000 && ch->SAR <= 0xe5204000) + dma_sleep_blocking[channel] = true; + if(ch->DAR >= 0xe5007000 && ch->DAR <= 0xe5204000) + dma_sleep_blocking[channel] = true; + return 0; } @@ -115,6 +125,9 @@ bool dma_transfer_async(int channel, dma_size_t size, uint blocks, dma_callbacks[channel] = callback; + if(dma_sleep_blocking[channel]) + sleep_block(); + /* Enable channel, starting the DMA transfer. */ channel_t *ch = dma_channel(channel); ch->CHCR.DE = 1; @@ -131,6 +144,9 @@ static void dma_interrupt_transfer_ended(int channel) DMA.OR.AE = 0; DMA.OR.NMIF = 0; + if(dma_sleep_blocking[channel]) + sleep_unblock(); + if(dma_callbacks[channel].function) { gint_call(dma_callbacks[channel]); @@ -151,18 +167,10 @@ void dma_transfer_wait(int channel) channel_t *ch = dma_channel(channel); if(!ch) return; - /* Wait for the channel to be disabled by the interrupt handler. - When the source or the destination of the transfer is X, Y or IL - memory, refrain from sleeping as this also stops the transfer! */ - int onchip = 0; - - if(ch->SAR >= 0xe5007000 && ch->SAR < 0xe5204000) onchip = 1; - if(ch->DAR >= 0xe5007000 && ch->DAR < 0xe5204000) onchip = 1; - while(ch->CHCR.DE && !ch->CHCR.TE) { /* Sleep only if the interrupt is enabled to wake us up */ - if(!onchip && ch->CHCR.IE) sleep(); + if(ch->CHCR.IE) sleep(); } }