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.
This commit is contained in:
Lephe 2020-06-17 11:36:05 +02:00
parent 8148d89c88
commit 2fd4238d31
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
8 changed files with 167 additions and 78 deletions

2
TODO
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <gint/timer.h> */
.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 */

View file

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