core: allow custom panics and exception catching

This change introduces two new mechanismes for executing user code when
an exception occurs.

* This first is the custom panic message, which usually displays "System
  ERROR". The function that performs this task can now be user-defined.
  It is also run in user mode because the exception handler rte's into
  it, allowing it to execute any kind of interrupt-inducing task. The
  behavior is undefined if this function raises an exception.

* The second is an exception-catching function, which (when set) is
  called every time an exception occurs, and is granted the chance of
  handling the exception to continue execution normally. It can be used
  in various ways, the most primitive of which is recording the
  exception and going back. It runs in interrupt mode and must not raise
  any kind of exception.
This commit is contained in:
Lephe 2019-09-13 08:10:30 +02:00
parent 9a4ae4c80a
commit 5630814897
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
7 changed files with 171 additions and 63 deletions

3
TODO
View file

@ -10,8 +10,7 @@ Tests to run.
* topti: all charsets
Complementary elements on existing code.
* core: make it possiblé to catch exceptions (gintctl's memory browser)
* core: check if -DX still works in GCC 9 and if not, put spaces
* topti: support unicode fonts
* gray: find good values for more models than the Graph 35+E II
* render: get rid of GINT_NEED_VRAM and #define vram gint_vram if you need
* dma: dma_memcpy() and dma_memset(), possibly requiring alignment

View file

@ -9,13 +9,58 @@
#ifndef GINT_EXC
#define GINT_EXC
/* gint_exc(): Exception handler fatal error message
Displays a full-screen fatal error message from an exception event code.
Some custom event codes are also used for library failure. This function is
typically called by exception handlers and returns after a dupdate_noint().
You probably want to soft-lock or quit the add-in after calling it. */
void gint_exc(uint32_t code);
#include <gint/defs/attributes.h>
#include <stdint.h>
/* TODO: Add functions to disable fatal errors for some exceptions */
/* gint_panic(): Panic handler function
This function is called when an uncaught CPU exception is generated. By
default, it displays a full-screen error message with the event code and
basic debugging information. Some custom event codes may be used for kernel
failure scenarios. This function never returns. */
GNORETURN void gint_panic(uint32_t code);
/* gint_panic_set(): Change the panic handler function
Sets up a different panic function instead of the default. It the argument
is NULL, restores the default. */
void gint_panic_set(GNORETURN void (*panic)(uint32_t code));
/* gint_exc_catch(): Set a function to catch exceptions
Sets up an exception-catching function. If an exception occurs, before a
panic is raised, the exception-catching function is executed in interrupt
mode and is given a chance to handle the exception. Passing NULL disables
this feature.
The exception-catching function can do anything that does not use interrupts
or causes an exception, such as logging the exception or any other useful
mechanism.
What happens next depends on the return value:
* If it returns 0, the exception is considered handled and execution
continues normally at or after the offending instruction.
* If it returns nonzero, a panic is raised.
Please be aware that many exceptions are of re-execution type. When
execution restarts after such an exception is handled, the offending
instruction if re-executed. This can cause the exception handling mechanism
to loop. Use gint_exc_skip() to skip over the offending instruction when
needed. Whether an exception is of re-execution type depends on the
exception code. */
void gint_exc_catch(int (*handler)(uint32_t code));
/* gint_exc_skip(): Skip pending exception instructions
Many exceptions re-execute the offending instruction after the exception is
handled. For instance the TLB miss handler is supposed to load the required
page into memory, so that the instruction that accessed unmapped memory can
be successfully re-executed.
When an exception-catching function records an exception without solving it,
this re-execution will fail again and the exception handling process will
loop. In such a situation, gint_exc_skip() can be used to manually skip the
offending instruction.
@instructions Number of instructions to skip (usually only one) */
void gint_exc_skip(int instructions);
#endif /* GINT_EXC */

View file

@ -1,13 +1,15 @@
#define GINT_NEED_VRAM
#include <gint/display.h>
#include <gint/exc.h>
#include <gint/display.h>
#include <gint/clock.h>
#include <gint/mpu/dma.h>
#include <gint/defs/attributes.h>
#define dprint(x, y, ...) dprint(x, y, C_BLACK, C_NONE, __VA_ARGS__)
#define dtext(x, y, str) dtext (x, y, str, C_BLACK, C_NONE)
/* gint_exc(): Exception handler fatal error message */
void gint_exc(GUNUSED uint32_t code)
/* gint_panic_default(): Default panic handler */
GNORETURN static void gint_default_panic(GUNUSED uint32_t code)
{
uint32_t TEA = *(volatile uint32_t *)0xff00000c;
uint32_t TRA = *(volatile uint32_t *)0xff000020 >> 2;
@ -82,17 +84,56 @@ void gint_exc(GUNUSED uint32_t code)
dtext(6, 95, "An unrecoverable error ocurred in the add-in.");
dtext(6, 108, "Please press the RESET button to restart the");
dtext(6, 121, "calculator.");
/* DMA address error handler */
if(code == 0x1020)
{
#define DMA SH7305_DMA
dprint(6, 141, "SAR0: %08x DAR0: %08x TCR0: %08x",
DMA.DMA0.SAR, DMA.DMA0.DAR, DMA.DMA0.TCR);
dprint(6, 154, "CHCR0: %08x", DMA.DMA0.CHCR);
dprint(6, 167, "SAR1: %08x DAR1: %08x TCR1: %08x",
DMA.DMA1.SAR, DMA.DMA1.DAR, DMA.DMA1.TCR);
dprint(6, 180, "CHCR1: %08x", DMA.DMA1.CHCR);
dprint(6, 193, "DMAOR: %04x", DMA.OR);
#undef DMA
}
#endif
dupdate_noint();
dupdate();
while(1) sleep();
}
/* gint_exch_tlbh(): Exception and TLB miss handler */
__attribute__((interrupt_handler)) GSECTION(".gint.exch_tlbh") GALIGNED(4)
void gint_exch_tlbh(void)
/* Panic handler */
GDATA GNORETURN void (*gint_exc_panic)(uint32_t code) = gint_default_panic;
/* Exception catcher */
GDATA int (*gint_exc_catcher)(uint32_t code) = NULL;
/* gint_panic(): Panic handler function */
void gint_panic(uint32_t code)
{
uint32_t EXPEVT = *(volatile uint32_t *)0xff000024;
gint_exc(EXPEVT);
while(1);
gint_exc_panic(code);
}
/* gint_panic_set(): Change the panic handler function */
void gint_panic_set(GNORETURN void (*panic)(uint32_t code))
{
gint_exc_panic = panic;
}
/* gint_exc_catch(): Set a function to catch exceptions */
void gint_exc_catch(int (*handler)(uint32_t code))
{
gint_exc_catcher = handler;
}
/* gint_exc_skip(): Skip pending exception instructions */
void gint_exc_skip(int instructions)
{
uint32_t spc;
/* Increase SPC by 2 bytes per instruction */
__asm__("stc spc, %0" : "=r"(spc));
spc += 2 * instructions;
__asm__("ldc %0, spc" :: "r"(spc));
}

53
src/core/exch.s Normal file
View file

@ -0,0 +1,53 @@
.global _gint_exch_tlbh
.section .gint.exch_tlbh
.align 4
_gint_exch_tlbh:
sts.l pr, @-r15
mov.l r8, @-r15
mov.l .expevt, r8
/* Panic if the catcher is NULL */
mov.l .catcher, r0
mov.l @r0, r0
tst r0, r0
bt panic
/* Leave if the catcher returns zero */
jsr @r0
mov.l @r8, r4
tst r0, r0
bt end
panic:
/* RTE to the panic function, but manually, so that SPC is preserved */
mov.l @r8, r4
ldc r4, r4_bank
mov.l @r15+, r8
lds.l @r15+, pr
/* Here we switch banks so r0..r7 change meaning! */
stc ssr, r0
ldc r0, sr
mov.l .panic, r0
mov.l @r0, r0
jmp @r0
nop
end:
mov.l @r15+, r8
lds.l @r15+, pr
rte
nop
.align 4
.expevt:
.long 0xff000024
.catcher:
.long _gint_exc_catcher
.panic:
.long _gint_exc_panic

View file

@ -134,12 +134,7 @@ int start(int isappli, int optnum)
bootlog_mapped(rom, ram);
/* Cancel add-in execution if not all pages are mapped */
if(rom < (uint32_t)&srom)
{
gint_exc(0x1040);
while(1);
return 1;
}
if(rom < (uint32_t)&srom) gint_panic(0x1040);
/* Install gint and switch VBR */
gint_install();

View file

@ -152,34 +152,6 @@ void dma_transfer_noint(int channel, dma_size_t size, uint blocks,
DMA.OR.NMIF = 0;
}
//---
// Address error handler
//---
/* gint_dma_ae(): DMA Address Error handler */
void gint_dma_ae(void)
{
gint_exc(0x1020);
#ifdef FXCG50
dprint(6, 141, C_BLACK, C_NONE,
"SAR0: %08x DAR0: %08x TCR0: %08x",
DMA.DMA0.SAR, DMA.DMA0.DAR, DMA.DMA0.TCR);
dprint(6, 154, C_BLACK, C_NONE,
"CHCR0: %08x", DMA.DMA0.CHCR);
dprint(6, 167, C_BLACK, C_NONE,
"SAR1: %08x DAR1: %08x TCR1: %08x",
DMA.DMA1.SAR, DMA.DMA1.DAR, DMA.DMA1.TCR);
dprint(6, 180, C_BLACK, C_NONE,
"CHCR1: %08x", DMA.DMA1.CHCR);
dprint(6, 193, C_BLACK, C_NONE,
"DMAOR: %04x", DMA.OR);
dupdate_noint();
#endif
while(1);
}
//---
// Initialization
//---

View file

@ -34,16 +34,19 @@ _inth_dma_te:
/* DMA ADDRESS ERROR INTERRUPT HANDLER - 18 BYTES */
_inth_dma_ae:
/* Call a fixed function */
sts.l pr, @-r15
mov.l 1f, r1
jsr @r1
nop
lds.l @r15+, pr
/* Manually RTE into the panic routine, preserving SPC */
mov.l 2f, r4
ldc r4, r4_bank
rte
/* This instruction changes register bank! */
stc ssr, r1
ldc r1, sr
mov.l 1f, r0
jmp @r0
nop
.zero 14
.zero 10
1: .long _gint_dma_ae
1: .long _gint_panic
2: .long 0x1020