user traps

General discussions about working with the Astrobe IDE and programming ARM Cortex-M0, M3, M4 and M7 microcontrollers.
Post Reply
steve64
Posts: 44
Joined: Mon Jul 09, 2018 8:56 am
Location: Italy

user traps

Post by steve64 » Thu Sep 13, 2018 9:12 am

It's not clear if it is possible to assign a procedure to a user-defined trap number.
I tried to do something like:

PROCEDURE MyTrapper;
BEGIN
Out.String("MY TRAPPER"); Out.Ln
END MyTrapper;

...
Traps.ShowRegs(FALSE); (* simplified trap log *)
Traps.Assign(100, MyTrapper);
ASSERT(FALSE, 100);
...

but I just get the standard trap error log:

Trap @080045C4H in Test, Line: 218, Code: 100

my procedure seems not invoked at all

cfbsoftware
Site Admin
Posts: 525
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: user traps

Post by cfbsoftware » Thu Sep 13, 2018 12:40 pm

ASSERT generates the ARM SVC instruction which is a software interrupt. The interrupt handler that is invoked is the one that is assigned to the address of the SVC interrupt handler (Traps.SVCVector). In a standard Astrobe installation the interrupt handler is the procedure Traps.SVCTrap. The assertion code is just displayed as a number in the error message.

You could use the assertion code to signify that the trap handler should do something different by modifying the source code of Traps.SVCTrap.

For example (* Warning: none of this has been compiled or tested *) in Traps you could declare an array of procedures:

Code: Select all

VAR
   userProcs: ARRAY 155 OF InterruptHandler;

and a procedure

Code: Select all

PROCEDURE AssignSVC*(code: INTEGER; p: InterruptHandler);
BEGIN
  IF code >= 100 THEN userProcs[code - 100] := p END
END AssignSVC;
and modify Traps.SVCTrap to call your function instead of executing the normal trap code:

Code: Select all

PROCEDURE SVCTrap;
 ...
  SYSTEM.GET(trapAddr - 4, instr);
  code := LSR(instr, 16) MOD 100H;
  (* modifications start here: *)
  procNo := code - 100;
  IF (procNo >= 0) & (userProcs[procNo] # NIL) THEN
      userProcs[procNo]
  ELSE ...
Don't forget to initialise the array in Traps.Init:

Code: Select all

 FOR i := 0 TO LEN(userProcs) - 1 DO userProcs[i] := NIL END;
Then in your own code you would write:

Code: Select all

Traps.AssignSVC(100, MyTrapper);
Thank you for prompting this idea. I like it and will consider implementing it in the standard Astrobe distribution ...

cfbsoftware
Site Admin
Posts: 525
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: user traps

Post by cfbsoftware » Sun Feb 10, 2019 9:49 pm

v7.0 of Astrobe for Cortex-M now supports user-customisable trap handlers. See the attached source code example of its use. The output from the example is:
UserTraps.gif
UserTraps.gif (2.57 KiB) Viewed 43816 times
Attachments
UserTraps.zip
User trap handler example
(669 Bytes) Downloaded 2256 times

gray
Posts: 143
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: user traps

Post by gray » Mon Feb 18, 2019 9:26 am

I want to use the SVC call/mechanism not only for error handling, but also executing code that is safe from interference by interrupt service routines (ISR), eg. to avoid race conditions. Thusly executed code must be small and fast for the least possible interference with the incoming interrupts. As Traps.mod is focused on error handling, there's a lot going on before a user trap handler is even called, including saving the regs, and collecting all the error info, which is not ideal for what I have in mind. However, I don't want to lose the error handling in Traps.mod by simply assigning my own handler to the SVC vector, so I adapted Traps.mod as outlined below.

Code: Select all

CONST
  ...
  SVC_Instruction = 0DF00H;

VAR
  ...
  svcHandler: InterruptHandler;

PROCEDURE SVCall*(h: InterruptHandler);
BEGIN
  svcHandler := h;
  SYSTEM.EMIT(SVC_Instruction)
END SVCall;

PROCEDURE SVCTrap;
VAR
  id: ID;
  regNo, addr, procNo: INTEGER;
BEGIN
  IF svcHandler # NIL THEN
    svcHandler;
    svcHandler := NIL
  ELSE
    SaveRegs();
    SYSTEM.GET(SYSTEM.SP + 128, id.addr);
    SYSTEM.GET(id.addr - 4, id.instr);
    ...
END SVCTrap;

PROCEDURE Init*;
VAR
  i, addr: INTEGER;
BEGIN
  ...
  svcHandler := NIL
END Init;
Now I can call

Code: Select all

Traps.SVCall(myproc)
to execute 'myproc' in handler mode with minimal overhead and still get the standard error handling.

Thoughts? Something I have overlooked?

PS: FWIW, my use case is a simple task scheduler using a ticker counter on the 'SysTick' interrupt, the value of which the scheduler loop needs to read out and reset.

PPS: I guess I also could swap out and then back in the SVC handler on the fly, if I wanted to avoid changes to a library module, which I am always reluctant to do for future compatibility; haven't tried that yet.

gray
Posts: 143
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: user traps

Post by gray » Mon Feb 18, 2019 10:12 am

Here's a version that works without changing Traps.mod, using the vector swapping technique (relevant excerpt from my Kernel.mod module, leaving out the SysTick business).

Code: Select all

CONST
  ...
    SVC_Instruction = 0DF00H;
    SVC_Vector = 02CH;

VAR
  ...
    sysHandler: PROCEDURE;
    svcTrapAddr: INTEGER;

PROCEDURE svcHandler;
BEGIN
  ASSERT(sysHandler # NIL, 100);
  sysHandler
END svcHandler;

PROCEDURE SVCall*(h: PROCEDURE);
VAR
  currentHandler: PROCEDURE;
BEGIN
  sysHandler := h;
  SYSTEM.GET(svcTrapAddr, currentHandler);
  Traps.Assign(svcTrapAddr, svcHandler);
  SYSTEM.EMIT(SVC_Instruction);
  SYSTEM.PUT(svcTrapAddr, currentHandler)
END SVCall;

PROCEDURE initSVC;
  VAR
    trapAddrBase: INTEGER;
BEGIN
  sysHandler := NIL;
  SYSTEM.GET(MCU.VTOR, trapAddrBase);
  svcTrapAddr := trapAddrBase + SVC_Vector;
END initSVC;

BEGIN
  initSVC
END ...

Caveat: I haven't done any in depth testing yet, but it works in my test set-up: the tasks get scheduled (SVCall works), and they can ASSERT errors (standard Traps work, ie. I am back to the unchanged Traps.mod here).

cfbsoftware
Site Admin
Posts: 525
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: user traps

Post by cfbsoftware » Tue Feb 19, 2019 12:08 pm

A couple of things I'm uneasy about that I think you should consider:

1. What are the consequences when the ASSERT in svcHandler fails? It might be sufficient just to skip the call to sysHandler if it is NIL.

2. What happens if sysHandler causes an exception?

gray
Posts: 143
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: user traps

Post by gray » Tue Feb 19, 2019 2:09 pm

ad 1.) true, that's a bad idea. It would result in a Hard fault. The ASSERT needs to be in SVCall:

Code: Select all

PROCEDURE SVCall*(h: PROCEDURE);
    VAR
      currentHandler: PROCEDURE;
  BEGIN
    ASSERT(h # NIL, 100);
    sysHandler := h;
    ...
ad 2.) as sysHandler runs as exception handler, it will result in a Hard fault. But that's true for UserHandlers set via Traps.mod as well, isn't it, as they also run as exception handlers, being called from SVCTrap. Or do I miss something?

Exactly for this reason, exception/interrupt handlers are tricky to write in general. I get uneasy, if they are longer than a few lines... ;)

cfbsoftware
Site Admin
Posts: 525
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: user traps

Post by cfbsoftware » Tue Feb 19, 2019 8:41 pm

gray wrote:ad 2.) as sysHandler runs as exception handler, it will result in a Hard fault. But that's true for UserHandlers set via Traps.mod as well, isn't it, as they also run as exception handlers, being called from SVCTrap. Or do I miss something?
Yes - you are right. The same thing occurred to me after I had posted my reply!

Back to your original question, you can probably reorganise the Traps code for user handlers so that it skips the code that saves registers, gets the module name etc. when making one of your SVC calls. You could do what ASSERT does and include a trap number with your SVC call so Traps can know whether it's an error trap or otherwise.

Post Reply