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.
This commit is contained in:
Lephe 2021-05-05 15:17:23 +02:00
parent 4c3fcf66a7
commit 95dbec17ab
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
5 changed files with 71 additions and 15 deletions

View File

@ -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

View File

@ -6,6 +6,9 @@
#define GINT_CLOCK
#include <gint/defs/types.h>
/* This header used to expose the sleep() function; include <gint/cpu.h> to
ensure this is still the case */
#include <gint/cpu.h>
//---
// 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

View File

@ -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
//---

22
src/cpu/sleep.c Normal file
View File

@ -0,0 +1,22 @@
#include <gint/cpu.h>
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();
}

View File

@ -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();
}
}