From 5bd04a961314e318bb913e54556a0c16335334e8 Mon Sep 17 00:00:00 2001 From: Lephe Date: Wed, 16 Jun 2021 21:50:41 +0200 Subject: [PATCH] cpu, dma: add interrupt-cancellable sleep (perfect async sleep) --- CMakeLists.txt | 1 + include/gint/cpu.h | 30 ++++++++++++++++++++++ src/cpu/ics.s | 64 ++++++++++++++++++++++++++++++++++++++++++++++ src/dma/dma.c | 26 ++++++++++++++++--- src/kernel/exch.c | 5 ++++ 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/cpu/ics.s diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ef5adb..420ab03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/gint/cpu.h b/include/gint/cpu.h index 3be2717..df4577c 100644 --- a/include/gint/cpu.h +++ b/include/gint/cpu.h @@ -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() 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 //--- diff --git a/src/cpu/ics.s b/src/cpu/ics.s new file mode 100644 index 0000000..7d37615 --- /dev/null +++ b/src/cpu/ics.s @@ -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: diff --git a/src/dma/dma.c b/src/dma/dma.c index 391a254..944d14d 100644 --- a/src/dma/dma.c +++ b/src/dma/dma.c @@ -8,6 +8,7 @@ #include #include #include +#include #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, diff --git a/src/kernel/exch.c b/src/kernel/exch.c index 7ecbed5..7540578 100644 --- a/src/kernel/exch.c +++ b/src/kernel/exch.c @@ -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();