From 2fd4238d3147a11e3fcaa98b3aeed368f928e943 Mon Sep 17 00:00:00 2001 From: Lephe Date: Wed, 17 Jun 2020 11:36:05 +0200 Subject: [PATCH] core: finalize TLB management in timer callbacks (STABLE) This change enables interrupts within timer callbacks, making it possible to load pages to MMU while handling a timer underflow. The call to TLB_LoadPTEH() has been moved directly into the VBR handler to avoid jumping to ILRAM for a short call on SH4. The TMU and ETMU handlers have been changed to callback through a new function gint_inth_callback() that saves the user bank and a few registers, then invokes the callback with interrupts enabled and in user bank; until now, callbacks were invoked with interrupts disabled and in kernel bank. Note that IMASK is still set so a callback can only be interrupted by a high-priority interrupt. A timer_wait() function has also been added to simplify tests that involve timers. Finally, the priority level of the TMU0 underflow interrupt has been set to 13 (as per the comments) instead of 7. This version is the first stable version that handles TLB misses transparently for large add-ins. It is suitable for every gint application. --- TODO | 2 +- include/gint/timer.h | 6 +++ src/core/inth.S | 69 +++++++++++++++++++++++++++++++++ src/core/syscalls.S | 23 +---------- src/core/tlbh.S | 22 +++++++++-- src/core/vbr.s | 4 +- src/tmu/inth.s | 92 ++++++++++++++++++++++++-------------------- src/tmu/tmu.c | 27 +++++++++---- 8 files changed, 167 insertions(+), 78 deletions(-) diff --git a/TODO b/TODO index f33b2e0..d46a7d7 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ For the 2.1.0 release: -* core: use gint_switch() to handle TLB misses * core: the four basic memory functions (with automated tests) * bopti: remove the deprecated image_t definition * project: remove the compat branch @@ -12,6 +11,7 @@ Issues: * #10 support fx-CG 20 Extensions on existing code: +* bopti: try to display fullscreen images with TLB access + DMA on fxcg50 * gray: add gprint() * gray: double-buffer gray settings and unify d* with g* * topti: support unicode fonts diff --git a/include/gint/timer.h b/include/gint/timer.h index 90b6076..da0e986 100644 --- a/include/gint/timer.h +++ b/include/gint/timer.h @@ -141,6 +141,12 @@ void timer_pause(int timer); @timer Timer id, as returned by timer_setup() */ void timer_stop(int timer); +/* timer_wait() - wait for a timer to stop + Waits until the specified timer stops running. If the timer is not running, + returns immediately. The timer might not be free if it has just been paused + instead of stopped entirely. */ +void timer_wait(int timer); + //--- // Predefined timer callbacks //--- diff --git a/src/core/inth.S b/src/core/inth.S index 153781f..25f7f07 100644 --- a/src/core/inth.S +++ b/src/core/inth.S @@ -11,6 +11,8 @@ .global _gint_inth_7705 #endif +.global _gint_inth_callback + .section .gint.blocks, "ax" .align 4 @@ -152,3 +154,70 @@ _inth_remap: .byte 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff #endif + +/* CALLBACK HELPER + This function implements the callback with context saving. It is a general + function and does not need to reside in VBR space which is block-structured. + This function saves registers r0_bank...r7_bank, enables interrupts, + switches back to user bank and executes the callback. It does not save other + registers (pr/mach/macl/gbr) which are managed by the handler entry. */ + +.section .gint.mapped + +/* gint_inth_callback() + Calls the specified function with the given argument after saving the user + context, enabling interrupts and going to user bank. + + @r4 Callback function (volatile void * -> int) + @r5 Argument (volatile void *) + Returns the return value of the callback (int). */ +_gint_inth_callback: + stc.l r0_bank, @-r15 + stc.l r1_bank, @-r15 + stc.l r2_bank, @-r15 + stc.l r3_bank, @-r15 + stc.l r4_bank, @-r15 + stc.l r5_bank, @-r15 + stc.l r6_bank, @-r15 + stc.l r7_bank, @-r15 + stc.l spc, @-r15 + stc.l ssr, @-r15 + stc.l sr, @-r15 + + /* Enable interrupts and go back to user bank. SR.IMASK is already set + to the level of the current interrupt, which makes sure we can only + be re-interrupted by something with a higher priority. */ + mov.l .SR_clear_RB_BL, r1 + stc sr, r0 + and r1, r0 + ldc r0, sr + + /* We are now in the user bank, but we can still use r0..r7 as the + important values have been saved on the stack. Call back. */ + stc r4_bank, r0 + sts.l pr, @-r15 + jsr @r0 + stc r5_bank, r4 + lds.l @r15+, pr + + /* We want to forward the return value to the system bank */ + ldc r0, r0_bank + + /* Restore the previous status register and the registers of the + interrupted procedure. */ + ldc.l @r15+, sr + ldc.l @r15+, ssr + ldc.l @r15+, spc + ldc.l @r15+, r7_bank + ldc.l @r15+, r6_bank + ldc.l @r15+, r5_bank + ldc.l @r15+, r4_bank + ldc.l @r15+, r3_bank + ldc.l @r15+, r2_bank + ldc.l @r15+, r1_bank + rts + ldc.l @r15+, r0_bank + +.align 4 +.SR_clear_RB_BL: + .long ~((1 << 29) | (1 << 28)) diff --git a/src/core/syscalls.S b/src/core/syscalls.S index c16cab4..4867939 100644 --- a/src/core/syscalls.S +++ b/src/core/syscalls.S @@ -9,8 +9,7 @@ ** * File system, because it's a mess and we might ruin the ROM. */ -/* TLB management */ -.global ___TLB_LoadPTEH +.section .pretext /* Dynamic allocation */ .global _malloc @@ -51,26 +50,6 @@ #define syscall(id) syscall_(id, syscall_table) -/*** Special syscalls that must remain mapped ***/ - -.section .gint.mapped - -#ifdef FX9860G -___TLB_LoadPTEH: - syscall_(0x0003, 2f) -2: .long 0x80010070 -#endif - -#ifdef FXCG50 -___TLB_LoadPTEH: - syscall_(0x000c, 2f) -2: .long 0x80020070 -#endif - -/*** Normal syscalls in ROM ***/ - -.section .pretext - #ifdef FX9860G /* Dynamic allocation */ diff --git a/src/core/tlbh.S b/src/core/tlbh.S index a0f8b73..f0b325c 100644 --- a/src/core/tlbh.S +++ b/src/core/tlbh.S @@ -28,8 +28,15 @@ test_tea: map: /* If TEA is mappable, map a page and return */ - mov.l .TLB_LoadPTEH, r0 - jsr @r0 + #ifdef FX9860G + mov #3, r0 + #endif + #ifdef FXCG50 + mov #12, r0 + #endif + + mov.l .syscall, r2 + jsr @r2 nop lds.l @r15+, macl @@ -66,5 +73,12 @@ panic: .long 0x00300000 .max_mapped_rom: .long 0x00300000 + _srom -.TLB_LoadPTEH: - .long ___TLB_LoadPTEH + +#ifdef FX9860G +.syscall: + .long 0x80010070 +#endif +#ifdef FXCG50 +.syscall: + .long 0x80020070 +#endif diff --git a/src/core/vbr.s b/src/core/vbr.s index 8335e6e..5e33666 100644 --- a/src/core/vbr.s +++ b/src/core/vbr.s @@ -11,8 +11,8 @@ priority registers or the interrupt masks, and make sure that all the interrupts that it leaves enabled are handled by the new VBR handlers. - @arg vbr New VBR address (uint32_t) - @arg configure Configuration function (void -> void) + @r4 vbr New VBR address (uint32_t) + @r5 configure Configuration function (void -> void) Returns the previous VBR address. */ _gint_setvbr: mov.l r9, @-r15 diff --git a/src/tmu/inth.s b/src/tmu/inth.s index a7ad4bb..2a32128 100644 --- a/src/tmu/inth.s +++ b/src/tmu/inth.s @@ -40,9 +40,8 @@ _inth_tmu_0: mova .storage0, r0 mov #0, r1 - /*** This is the first shared section ***/ - -.clearflag: +/*** This is the first shared section ***/ +.shared1: sts.l pr, @-r15 mov.l r1, @-r15 @@ -54,32 +53,31 @@ _inth_tmu_0: mov.w r2, @r1 /* Invoke the callback function and pass the argument */ - mov.l @r0, r1 + mov.l .gint_inth_callback_1, r1 + mov.l @r0, r4 jsr @r1 - mov.l @(4, r0), r4 + mov.l @(4, r0), r5 - /* Prepare stopping the timer and jump to second section */ + /* Jump to second section */ + mov.l .timer_stop_1, r1 + bra .shared2 mov.l @r15+, r4 - mov.l .timer_stop, r1 - bra .stoptimer - nop /* SECOND GATE - TMU1 entry and stop timer */ _inth_tmu_1: mova .storage1, r0 - bra .clearflag + bra .shared1 mov #1, r1 - /*** This is the second shared section ***/ - -.stoptimer: +/*** This is the second shared section ***/ +.shared2: /* Stop the timer if the return value is not zero */ tst r0, r0 - bt .end + bt .shared3 jsr @r1 nop -.end: +.shared3: lds.l @r15+, pr rts nop @@ -89,10 +87,13 @@ _inth_tmu_1: /* THIRD GATE - TMU2 entry and storage for TMU0 */ _inth_tmu_2: mova .storage2, r0 - bra .clearflag + bra .shared1 mov #2, r1 - .zero 14 + .zero 10 + +.gint_inth_callback_1: + .long _gint_inth_callback .storage0: .long 0 /* Callback: Configured dynamically */ @@ -104,8 +105,8 @@ _inth_tmu_storage: .mask: .long 0x0000feff -.timer_stop: - .long _timer_stop /* gint's function from */ +.timer_stop_1: + .long _timer_stop .storage1: .long 0 /* Callback: Configured dynamically */ @@ -142,27 +143,24 @@ _inth_tmu_storage: /* FIRST GATE - ETMU2 entry, invoke callback and prepare clear flag */ _inth_etmu2: - /* Warning: the size of the following section (4 bytes) is hardcoded in - the jump in _inth_etmux */ mova .storage_etmu2, r0 mov #5, r1 -.extra_callback: +.shared4: sts.l pr, @-r15 - mov.l r1, @-r15 + mov.l r0, @-r15 - /* Invoke the callback function */ - mov.l @r0, r1 - jsr @r1 - mov.l @(4, r0), r4 + /* Clear interrupt flag */ + mov.l .timer_clear, r2 + jsr @r2 + mov r1, r4 - /* Load timer ID and forward the callback's return value */ - mov.l .timer_clear, r1 - mov.l @r15+, r4 + /* Prepare invoking the callback function */ + mov.l @r15+, r0 + mov.l .gint_inth_callback_2, r1 + mov.l @r0, r4 bra _inth_etmu_help - mov r0, r5 - - nop + mov.l @(4, r0), r5 .storage_etmu2: .long 0 /* Callback: Configured dynamically */ @@ -171,18 +169,28 @@ _inth_etmu2: /* SECOND GATE - Helper entry, invoke callback and stop timer if requested */ _inth_etmu_help: - /* Clear the flag and possibly stop the timer */ + /* Invoke callback; if return value is non-zero, stop timer */ + jsr @r1 + nop + tst r0, r0 + bt .shared5 + mov.l .timer_stop_2, r1 jsr @r1 nop + /* Clear the flag and possibly stop the timer */ + +.shared5: lds.l @r15+, pr rts nop - .zero 18 - +.gint_inth_callback_2: + .long _gint_inth_callback .timer_clear: - .long _timer_clear /* gint's function from src/tmu/tmu.c */ + .long _timer_clear +.timer_stop_2: + .long _timer_stop /* THIRD GATE - All other ETMU entries, deferred to the previous ones */ _inth_etmux: @@ -198,11 +206,11 @@ _inth_etmux: nop /* Offset from VBR where extra timer 2 is located: - - 0x600 to reach the interrupt handlers - - 0x040 to jump over the entry gate - - 0x840 to reach the handler of ETMU2 - - 0x004 to skip its first instructions (the size is hardcoded) */ -1: .long 0xe84 + * 0x600 to reach the interrupt handlers + * 0x040 to jump over the entry gate + * 0x840 to reach the handler of ETMU2 + * Skip over the first instructions */ +1: .long 0xe80 + (.shared4 - _inth_etmu2) .id_etmux: .long 0 /* Timer ID */ diff --git a/src/tmu/tmu.c b/src/tmu/tmu.c index 3dd56b4..4b9a111 100644 --- a/src/tmu/tmu.c +++ b/src/tmu/tmu.c @@ -162,6 +162,22 @@ GMAPPED void timer_stop(int id) } } +/* timer_wait() - wait until a timer is stopped */ +void timer_wait(int id) +{ + if(id < 3) + { + tmu_t *T = &TMU[id]; + /* Sleep if an interruption will wake us up */ + while(*TSTR & (1 << id)) if(T->TCR.UNIE) sleep(); + } + else + { + etmu_t *T = &ETMU[id]; + while(T->TSTR) if(T->TCR.UNIE) sleep(); + } +} + //--- // Predefined timer callbacks //--- @@ -178,15 +194,12 @@ GMAPPED int timer_timeout(volatile void *arg) // Low-level functions //--- -/* timer_clear() - clear an ETMU flag and possibly stop the timer - @timer Timer ID, must be an ETMU - @stop Non-zero to stop the timer */ -GMAPPED void timer_clear(int id, int stop) +/* timer_clear() - clear an ETMU flag + @timer Timer ID, must be an ETMU */ +GMAPPED void timer_clear(int id) { do ETMU[id-3].TCR.UNF = 0; while(ETMU[id-3].TCR.UNF); - - if(stop) timer_stop(id); } //--- @@ -270,7 +283,7 @@ static void init(void) gint_inthandler(0xc60, inth_etmu_help, 32); /* Enable TMU0 at level 13, TMU1 at level 11, TMU2 at level 9 */ - gint_intlevel(0, 7); + gint_intlevel(0, 13); gint_intlevel(1, 11); gint_intlevel(2, 9);