Overview
These example/test programs explore triggering an exception from software in an strictly synchronous fashion using the SVC
instruction and its exception handler, in lieu of setting the pending bit of an exception, followed by the necessary memory barriers. The memory barriers ensure that the mutation of the corresponding NVIC registers in the System Control Space is concluded before the MCU executes any further instructions. This way, the pending exception is acknowledged immediately, and executes synchronously to the triggering code. Using the SVC
instruction always results in an immediate, synchronous execution of the corresponding exception, without the need for memory barriers.
SVC
Astrobe uses the SVC
instruction for run-time error handling, ie. errors detected by compiler-inserted tests, eg. for index out of bounds, or division by zero. Astrobe’s module Traps
, and RTK’s module RuntimeErrors
, define corresponding exception handlers, and install it in both cores’ vector tables at the start of the program.
If we want to use the SVC
instruction for other purposes, such as system traps, we have to decouple that fixed relationship between the SVC
instruction and the run-time error handler.
SysCall
The example program directory contains module SysCall
for this purpose:
SysCall
:
- implements a handler
svch
for theSVC
exception; - patches both vector tables to execute
svch
in lieu of the run-time error handler viaSysCall.Init
.
SVC Exception Handler
The SVC
instruction can be “parametrised” using an immediate 8-bit value. Astrobe uses this value to signal the error number, and in a generalised set-up we want to use it to determine which system trap functionality to execute.
PROCEDURE svch[0];
VAR icsr, stackframeAddr, retAddr: INTEGER; svcVal: BYTE;
BEGIN
(* get base address of exception stack frame *)
IF EXC_RET_SPSEL IN BITS(SYSTEM.REG(LR)) THEN (* PSP used for stacking *)
SYSTEM.EMIT(MCU.MRS_R11_PSP); (* get PSP via r11 *)
stackframeAddr := SYSTEM.REG(11)
ELSE (* MSP used for stacking *)
stackframeAddr := SYSTEM.REG(SP) + 20; (* +20: local vars and lr *)
END;
(* get imm svc value *)
SYSTEM.GET(stackframeAddr + PCoffset, retAddr); (* return address *)
SYSTEM.GET(retAddr - 2, svcVal); (* svc instr is two bytes, imm value is lower byte *)
(* finally, act on svc value *)
IF svcVal <= MaxErrorNo THEN (* error handling via RuntimeErrors *)
SYSTEM.GET(MCU.PPB_ICSR, icsr);
icsr := ORD(BITS(icsr) + {PENDSVSET});
SYSTEM.PUT(MCU.PPB_ICSR, icsr); (* set PendSV pending *)
SYSTEM.EMIT(MCU.DSB); SYSTEM.EMIT(MCU.ISB);
ELSE (* program/application specific sys calls: set ints pending *)
IF svcVal = SVCvalIRQ0 THEN
SYSTEM.PUT(MCU.PPB_NVIC_ISPR0 + ((IntNo0 DIV 32) * 4), {IntNo0 MOD 32});
SYSTEM.EMIT(MCU.DSB); SYSTEM.EMIT(MCU.ISB)
ELSIF svcVal = SVCvalIRQ1 THEN
SYSTEM.PUT(MCU.PPB_NVIC_ISPR0 + ((IntNo1 DIV 32) * 4), {IntNo1 MOD 32});
SYSTEM.EMIT(MCU.DSB); SYSTEM.EMIT(MCU.ISB)
END;
END
END svch;
Hence, the exception handler
- determines the stack on which the stacking for the
SVC
exception has occurred (main or process stack); - extracts the immediate
SVC
value, via the exception return address on the stack; - if the
SVC
value is in the range of the run-time errors, thePendSV
exception is set pending; - if the
SVC
value is greater that the run-time error values, the system trap logic is executed.
That is, the run-time errors are now handled by PendSV
in lieu of SVC
. The PendSV
exception will get triggered as soon as the SVC
exception returns, and use the same stack frame as SVC
due to tail-chaining, hence all the data required for the run-time error handling is right there on the stack, just as if the run-time error handling had been done by the SVC
handler.
Using PendSV
is just one possibility of course, we could have also simply called the run-time error handler from the SVC
handler. I just wanted to finally explore the rather obscure PendSV
exception. :)
In case SVC
does not indicate a run-time error, the system or program specific trap handler logic gets executed. In the example program’s SysCall
, it triggers either of two interrupts, depending on the SVC
immediate value, so I can lazily re-use the test programs from Stacktrace. Again, as with run-time error case, we could also call corresponding service procedures from the SVC
handler.
However, calling procedures to handle system traps from the SVC
handler obviously means that the corresponding code is executed as part of the handler, and any run-time error cannot be handled via the SVC
instruction and exception. As coded above, svch
also avoids calling utility procedures, such as setting an interrupt pending via Exceptions.SetPendingInt
in order to avoid the involved overhead of pushing all registers in software that are not stacked by the exception entry hardware. This may or may not be relevant for a specific use case.
Patching the Vector Tables
To enable the above logic, SysCall.Init
installs
svch
asSVC
exception handler, andRuntimeErrors.ErrorHandler
asPendSV
exception handler.
SysCall.Init
is implemented to be called from each core separately.
Custom Module
SysCall
is a custom module for now, ie. to be implemented for specific use cases.
Example Result
Refer to Stacktrace for a description of the test programs, which are used here in slightly adapted versions.
Running StacktrK2C1
gives:
run-time error in handler mode: 7 core: 0
integer divided by zero or negative divisor
StacktrK2C1.error addr: 100057D0H ln: 43
trace:
StacktrK2C1.error 100057D0H 43 2003FA58H
StacktrK2C1.i2 100057F8H 51 2003FA64H
StacktrK2C1.i1 10005812H 59 2003FA6CH
StacktrK2C1.i0 10005820H 64 2003FA70H
--- exc ---
StacktrK2C1.h2 10005830H 2003FA98H
StacktrK2C1.h1 10005864H 83 2003FB00H
StacktrK2C1.h0 10005874H 88 2003FB0CH
--- exc ---
StacktrK2C1.p1 10005890H 2003F200H
StacktrK2C1.p0 100058A2H 107 2003F220H
StacktrK2C1.run 100058C2H 112 2003F224H
StacktrK2C1.t0c 100058D2H 118 2003F228H
stacked registers:
psr: 6900023FH
pc: 100057D2H
lr: 100057FDH
r12: 0A0B0C0DH
r3: 00000000H
r2: 2000032CH
r1: 00000000H
r0: 00000000H
sp: 2003FA40H
current registers:
sp: 2003FA0CH
lr: FFFFFFF1H
pc: 10002D34H
psr: 0800000EH
That is, the SVC
handler in module SysCall
correctly triggers the two application exceptions h0
and i0
, and then handles the run-time error in StacktrK2C1.error
.
PROCEDURE p1;
BEGIN
(* set int for h0 pending *)
SYSTEM.EMITH(SVCinstIRQ0);
p1a
END p1;
PROCEDURE h2;
BEGIN
(* set int for i0 pending *)
SYSTEM.EMITH(SVCinstIRQ1)
END h2;
Note the exception number E
in the current psr
, which is the exception number of PendSV
.
Bottom Line
Module SysCall
demonstrates how the SVC
instruction and the corresponding exception handler can be generalised and decoupled from Astrobe’s run-time error handling, in order to also be used for system traps, for example as means to grant exclusive mutating access to shared data, or other protected functionality or hardware.
Output Terminal
See Set-up, two-terminal set-up.
Build and Run
Repository
- for RP2040/Pico and RP2350/Pico2: SysCall