SysCall

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 the SVC exception;
  • patches both vector tables to execute svch in lieu of the run-time error handler via SysCall.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

  1. determines the stack on which the stacking for the SVC exception has occurred (main or process stack);
  2. extracts the immediate SVC value, via the exception return address on the stack;
  3. if the SVC value is in the range of the run-time errors, the PendSV exception is set pending;
  4. 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 as SVC exception handler, and
  • RuntimeErrors.ErrorHandler as PendSV 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