forked from Lephenixnoir/gint
Lephe
2fd4238d31
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.
220 lines
6.1 KiB
ArmAsm
220 lines
6.1 KiB
ArmAsm
/*
|
|
** gint:tmu:inth - Interrupt handlers for the timer units
|
|
** Perhaps the most technical of my interrupt handlers. They implement a
|
|
** simple kind of interrupt handler communication by letting the control flow
|
|
** from each interrupt handler to the next.
|
|
*/
|
|
|
|
/* Gates for the standard Timer Unit (TMU) */
|
|
.global _inth_tmu /* 128 bytes */
|
|
|
|
/* Gates for the extra timers (informally called ETMU) */
|
|
.global _inth_etmu2 /* 32 bytes */
|
|
.global _inth_etmu_help /* 32 bytes */
|
|
.global _inth_etmux /* 32 bytes */
|
|
|
|
.section .gint.blocks, "ax"
|
|
.align 4
|
|
|
|
/* TMU INTERRUPT HANDLERS - 128 BYTES
|
|
Unfortunately I did not manage to write a handler that cleared the interrupt
|
|
flag and invoked a callback in less than 34 bytes data included. So I
|
|
decided to make several gates operate as a whole and add a bit more features
|
|
in them. Basically, these handlers:
|
|
- Clear the interrupt flag
|
|
- Invoke a callback function and pass it a user-provided argument
|
|
- Stop the timer if the callback returns non-zero
|
|
- Host their own callback pointers and arguments
|
|
|
|
It is important to notice that the code of the following gates looks like
|
|
they are contiguous in memory. The assembler will make that assumption, and
|
|
turn any address reference between two gates into a *relative displacement*.
|
|
If the gates don't have the same relative location at runtime, the code will
|
|
crash because we will have broken the references. This is why we can only do
|
|
it with handlers that are mapped to consecutive event codes. */
|
|
|
|
_inth_tmu:
|
|
|
|
/* FIRST GATE - TMU0 entry, clear underflow flag and call back */
|
|
_inth_tmu_0:
|
|
mova .storage0, r0
|
|
mov #0, r1
|
|
|
|
/*** This is the first shared section ***/
|
|
.shared1:
|
|
sts.l pr, @-r15
|
|
mov.l r1, @-r15
|
|
|
|
/* Load the TCR address and clear the interrupt flag */
|
|
mov.l .mask, r3
|
|
mov.l @(8, r0), r1
|
|
mov.w @r1, r2
|
|
and r3, r2
|
|
mov.w r2, @r1
|
|
|
|
/* Invoke the callback function and pass the argument */
|
|
mov.l .gint_inth_callback_1, r1
|
|
mov.l @r0, r4
|
|
jsr @r1
|
|
mov.l @(4, r0), r5
|
|
|
|
/* Jump to second section */
|
|
mov.l .timer_stop_1, r1
|
|
bra .shared2
|
|
mov.l @r15+, r4
|
|
|
|
/* SECOND GATE - TMU1 entry and stop timer */
|
|
_inth_tmu_1:
|
|
mova .storage1, r0
|
|
bra .shared1
|
|
mov #1, r1
|
|
|
|
/*** This is the second shared section ***/
|
|
.shared2:
|
|
/* Stop the timer if the return value is not zero */
|
|
tst r0, r0
|
|
bt .shared3
|
|
jsr @r1
|
|
nop
|
|
|
|
.shared3:
|
|
lds.l @r15+, pr
|
|
rts
|
|
nop
|
|
|
|
.zero 12
|
|
|
|
/* THIRD GATE - TMU2 entry and storage for TMU0 */
|
|
_inth_tmu_2:
|
|
mova .storage2, r0
|
|
bra .shared1
|
|
mov #2, r1
|
|
|
|
.zero 10
|
|
|
|
.gint_inth_callback_1:
|
|
.long _gint_inth_callback
|
|
|
|
.storage0:
|
|
.long 0 /* Callback: Configured dynamically */
|
|
.long 0 /* Argument: Configured dynamically */
|
|
.long 0xa4490010 /* TCR0: Overridden at startup on SH3 */
|
|
|
|
/* FOURTH GATE - Storage for TMU1, TMU2 and other values */
|
|
_inth_tmu_storage:
|
|
|
|
.mask:
|
|
.long 0x0000feff
|
|
.timer_stop_1:
|
|
.long _timer_stop
|
|
|
|
.storage1:
|
|
.long 0 /* Callback: Configured dynamically */
|
|
.long 0 /* Argument: Configured dynamically */
|
|
.long 0xa449001c /* TCR1: Overridden at startup on SH3 */
|
|
|
|
.storage2:
|
|
.long 0 /* Callback: Configured dynamically */
|
|
.long 0 /* Argument: Configured dynamically */
|
|
.long 0xa4490028 /* TCR2: Overridden at startup on SH3 */
|
|
|
|
|
|
|
|
/* EXTRA TMU INTERRUPT HANDLERS - 96 BYTES
|
|
To implement the same functionalities as the standard timers, several blocks
|
|
are once again needed. But the handlers for the extra timers are not located
|
|
in adjacent gates, except for ETMU1 and ETMU2 which have event codes 0xc20
|
|
and 0xc40. Since handler 0xc60 is free on SH4 and not redirected to on SH3,
|
|
I use it to build a three-handler block similar to that of the TMU above.
|
|
|
|
On SH4 this means that an extra gate has to be installed, but no interrupt
|
|
point here. On SH3 this means that four gates are used for the only extra
|
|
timer, but the incurred cost is minimal (96 bytes on the binary file)
|
|
because the size of the VBR area can hardly be shrunk anyway.
|
|
|
|
It *is* possible to do generalized communication between interrupt handlers
|
|
that do not reside in consecutive gates. The general way of performing a
|
|
jump or data access between two interrupt handlers would be to store at
|
|
runtime the address of the target resource in a reserved longword in the
|
|
source handler. But longwords are costly in 32-byte areas. Even if the event
|
|
codes of the interrupt handlers are known at development time, the best I
|
|
can think of is hardcoding the relative displacements, and one would need to
|
|
use the unnatural and unmaintainable @(disp, pc) addressing modes. */
|
|
|
|
/* FIRST GATE - ETMU2 entry, invoke callback and prepare clear flag */
|
|
_inth_etmu2:
|
|
mova .storage_etmu2, r0
|
|
mov #5, r1
|
|
|
|
.shared4:
|
|
sts.l pr, @-r15
|
|
mov.l r0, @-r15
|
|
|
|
/* Clear interrupt flag */
|
|
mov.l .timer_clear, r2
|
|
jsr @r2
|
|
mov r1, 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.l @(4, r0), r5
|
|
|
|
.storage_etmu2:
|
|
.long 0 /* Callback: Configured dynamically */
|
|
.long 0 /* Argument: Configured dynamically */
|
|
|
|
/* SECOND GATE - Helper entry, invoke callback and stop timer if requested */
|
|
_inth_etmu_help:
|
|
|
|
/* 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
|
|
|
|
.gint_inth_callback_2:
|
|
.long _gint_inth_callback
|
|
.timer_clear:
|
|
.long _timer_clear
|
|
.timer_stop_2:
|
|
.long _timer_stop
|
|
|
|
/* THIRD GATE - All other ETMU entries, deferred to the previous ones */
|
|
_inth_etmux:
|
|
/* Dynamically compute the target of the jump */
|
|
stc vbr, r3
|
|
mov.l 1f, r2
|
|
add r2, r3
|
|
|
|
mova .storage_etmux, r0
|
|
mov.l .id_etmux, r1
|
|
jmp @r3
|
|
nop
|
|
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
|
|
* Skip over the first instructions */
|
|
1: .long 0xe80 + (.shared4 - _inth_etmu2)
|
|
|
|
.id_etmux:
|
|
.long 0 /* Timer ID */
|
|
.storage_etmux:
|
|
.long 0 /* Callback: Configured dynamically */
|
|
.long 0 /* Argument: Configured dynamically */
|