cpu, dma: add interrupt-cancellable sleep (perfect async sleep)

This commit is contained in:
Lephe 2021-06-16 21:50:41 +02:00
parent 658413ba19
commit 5bd04a9613
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
5 changed files with 123 additions and 3 deletions

View File

@ -25,6 +25,7 @@ set(SOURCES_COMMON
src/cpg/cpg.c
src/cpu/atomic.c
src/cpu/cpu.c
src/cpu/ics.s
src/cpu/registers.s
src/cpu/sleep.c
src/dma/dma.c

View File

@ -111,6 +111,36 @@ void sleep_block(void);
/* sleep_unblock(): Cancel a processor sleep block */
void sleep_unblock(void);
//---
// Interrupt-cancellable sleeps
//
// The sleep() function has the significant drawback of not synchronizing with
// interrupts. Programs usually run [while(<interrupt not occurred>) sleep()],
// which fails if the interrupt occurs between the time the condition is
// checked and time the sleep instruction is executed.
//
// Interrupt-cancellable sleep is a software mechanism by which the interrupt
// disables the sleep instruction itself (by replacing it with a nop), which
// ensures that the CPU cannot go to sleep after the interrupt occurs.
//---
/* cpu_csleep_t: A cancellable sleep function
This object holds sleep code that can be disabled by an interrupt. The size
and layout of this variable is dependent on some assembler code. This should
be allocated on the stack, heap, or on-chip memory, because the data segment
is not executable! */
typedef GALIGNED(4) uint8_t cpu_csleep_t[20];
/* cpu_csleep_init(): Create an ICS function
This function initializes the provided ICS routine. */
void cpu_csleep_init(cpu_csleep_t *ics);
/* cpu_csleep(): Run the sleep function until the sleep is cancelled */
void cpu_csleep(cpu_csleep_t *ics);
/* cpu_csleep_cancel(): Cancel the sleep function from within an interrupt */
void cpu_csleep_cancel(cpu_csleep_t *ics);
//---
// Configuration
//---

64
src/cpu/ics.s Normal file
View File

@ -0,0 +1,64 @@
.global _cpu_csleep_init
.global _cpu_csleep
.global _cpu_csleep_cancel
_cpu_csleep_init:
mov.l .memcpy, r1
mova sleep, r0
mov r0, r5
jmp @r1
mov #(sleep_end - sleep), r6
.align 4
.memcpy:
.long _memcpy
_cpu_csleep:
mov.l r8, @-r15
sts.l pr, @-r15
mov r4, r8
/* Check if the sleep instruction is still there */
1: mov.w @(8, r8), r0
extu.w r0, r0
cmp/eq #0x001b, r0
bf 2f
/* Invalidate the cache in case of previous ICS being cached */
mov r8, r0
icbi @r0
add #18, r0
icbi @r0
/* Execute the sleep, and loop */
jsr @r8
nop
bra 1b
nop
2: lds.l @r15+, pr
rts
mov.l @r15+, r8
_cpu_csleep_cancel:
mov #0x0009, r0
add #8, r4
mov.w r0, @r4
icbi @r4
rts
nop
.align 4
/* This is identical in functionality to the CPU driver's sleep() function */
sleep:
mov.l 2f, r0
mov.l @r0, r0
cmp/pl r0
bt 1f
sleep
1: rts
nop
nop
2: .long _cpu_sleep_block_counter
sleep_end:

View File

@ -8,6 +8,7 @@
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/exc.h>
#include <gint/cpu.h>
#define DMA SH7305_DMA
#define POWER SH7305_POWER
@ -18,6 +19,8 @@ typedef volatile sh7305_dma_channel_t channel_t;
static gint_call_t dma_callbacks[6] = { 0 };
/* Sleep blocking flags for all channels */
static bool dma_sleep_blocking[6] = { 0 };
/* ICS for dma_channel_wait() for all channels */
static cpu_csleep_t *dma_wait_ics[6] = { 0 };
/* dma_channel(): Get address of a DMA channel */
static channel_t *dma_channel(int channel)
@ -147,6 +150,10 @@ static void dma_interrupt_transfer_ended(int channel)
if(dma_sleep_blocking[channel])
sleep_unblock();
/* Cancel any sleep operation that is synchronized with this interrupt */
if(dma_wait_ics[channel])
cpu_csleep_cancel(dma_wait_ics[channel]);
if(dma_callbacks[channel].function)
{
gint_call(dma_callbacks[channel]);
@ -167,11 +174,24 @@ void dma_transfer_wait(int channel)
channel_t *ch = dma_channel(channel);
if(!ch) return;
while(ch->CHCR.DE && !ch->CHCR.TE)
/* Interrupt disabled: spin-wait */
if(!ch->CHCR.IE)
{
/* Sleep only if the interrupt is enabled to wake us up */
if(ch->CHCR.IE) sleep();
while(ch->CHCR.DE && !ch->CHCR.TE) {}
return;
}
/* Initialize an interrupt-cancellable sleep, to ensure synchronization */
cpu_csleep_t ics;
cpu_csleep_init(&ics);
dma_wait_ics[channel] = &ics;
/* Now the ICS is set; if the interrupt has not occurred yet then the
handler is guaranteed to cancel the sleep at some point */
if(ch->CHCR.DE && !ch->CHCR.TE) cpu_csleep(&ics);
/* Clear the ICS pointer for next time */
dma_wait_ics[channel] = NULL;
}
bool dma_transfer_sync(int channel, dma_size_t size, uint length,

View File

@ -114,6 +114,11 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code)
dprint(6, 193, "DMAOR: %04x", DMA.OR);
#undef DMA
}
/* Illegal instruction handler */
if(code == 0x180)
{
dprint(6, 141, "Opcode: %04x", *(uint16_t *)PC);
}
#endif
dupdate();