The Exception Handling module

Introduction - - One integral part of any HAL must be to provide for the handling of processor exceptions. This support provided here goes considerably further than most in providing for exception handling. This includes:
  1. Support for handlers written in APCS C and ARM assembler
  2. Handling is completely static and reentrant
  3. Provision for the passing of a parameter to the handler
  4. Support for the early termination of the handler calling chain
  5. Support for the use of unlimited handlers (defined in the Board Definition)
  6. Support for the prioritisation of handlers
  7. Provision for the installation and use of a former handler of a vector (intended for chaining a debugger into the exception handling mechanism or quick reuse of older code)
Callability
Assembler:  INCLUDE "ExceptH.s"
C:  #include "ExceptH.h"
HALError
Module name:  "ExceptH", 0 (0x65637845, 0x00487470)
Codes:
0:  (0x00):  Reserved
1:  (0x01):  Reserved
2:  (0x02):  All handler slots filled
3:  (0x03):  Unable to find handler
128:  (0x80):  Undefined Instruction Exception unclaimed
129:  (0x81):  SWI Exception unclaimed
130:  (0x82):  Abort on Instruction Fetch Exception unclaimed
131:  (0x83):  Address Exception unclaimed
132:  (0x84):  Reserved
133:  (0x85):  IRQ unclaimed
134:  (0x86):  FIQ unclaimed

Note that when codes with bit 7 set (128-133) appear it is always in the form of a system halt

Portability API is portable
Code not portable
Constituents Entirely written in ARM assembler for speed
Porting notes API will be very similar across platforms. This will ease porting of code which uses this module - hence the not including of ARM in the module's name or prefix.
Other Notes The following values in BoardDefines determine how many handlers exist for each ARM vector:
  • UndefinedExcptHandlers
  • SWIExcptHandlers
  • PAbortExcptHandlers
  • DAbortExcptHandlers
  • IRQExcptHandlers
  • FIQExcptHandlers
General use All calls to and from the exception handling module are prefixed by ExceptH_. The module uses only zero page memory and hence may be called at times when main memory is not functional.

To initialise and finalise the operation of this module, the usual calls ExceptH_Initialise and ExceptH_Finalise are provided. The API's ExceptH_AddHandler and ExceptH_RemoveHandler permit the installation and removal of exception handlers. Control of the order in which handlers are called when the exception occurs is provided - through the use of bit 0 in the flags when calling ExceptH_AddHandler one may place the new handler at the top of the list or at the end of the list.

The module also features an extremely low overhead when handling exceptions. It is hand-written in ARM assembler for speed and a handler can be called with as little as a twenty instruction overhead. The ExceptH end of the handling code is also reentrant, allowing (say) interrupts to be reenabled during the execution of an interrupt handler.

To install the module over an existing exception handling system (eg; an OS or debugger like Demon or Angel) is easy. Initialise the module, perform an ExceptH_AddHandler on each of the relevent vectors with bit 2 set. This will unwind the stack and return entry registers to those of module entry before calling. This is done for you during the HAL startup code when DEMON or ANGEL builds are performed.

On non-STANDALONE builds bit 0 in ExceptH_AddHandler is forced set as any former handler type entry prevents calls to those handlers after it in the list. It is good form to ensure your handler is capable of being called before and after any other handler and this has the benefit of not introducing an unneeded bug.

You may also add the same handler many times for the same vector. It is your responsibility to remove the handler the same number of times. If you are unsure how many times a handler has been added, repeatedly call ExceptH_RemoveHandler until it returns an error.

Handler format:
ExceptH handlers (ie; ones installed without bit 2 set) have registers R0-R3 available for use and may use the other registers as permitted by APCS (ie; it is safe to have the handler written in C - do not use __irq though). You may also pass a parameter to the handler by specifying it in R2 when calling ExceptH_AddHandler (which will appear in the first integer parameter of a APCS C function (ie; R0) unless bit 1 has been set in which case it will appear in R12).

The environment just prior to the call can be discovered via the contents of the stack as passed to the handler:

Offset Contents
+20: R3
+16: R2
+12: R1
+8: R0
+4: R14
(address at which exception occured)
sp+0: SPSR
(CPSR when exception occured)

The SPSR is the Saved Processor Status Register and the CPSR is the Current Processor Status Register which holds the flags used by the ARM processor. This environment block allows a SWI handler to discover what SWI number it is or a virtual memory driver to discover what page of memory has faulted and needs loading. A routine wishing to alter R0-R3 on return may do so by altering the appropriate registers on the stack and altering R4-R7 on return may be done so directly as these are not used by the ExceptH handling routine.

In addition, handlers must return a value in R0 to specify what is to be done by the system. These values can be:

  • 0 to indicate call next handler on list
  • 1 to indicate not to call next handler on list (this is called early-termination of the handling)
  • Any other value to indicate this is a pointer to a HALError block (ie; an error occured during the execution of the handler)

Note that any errors returned via R0 this way have nowhere to go. Hence the ExceptH handler will perform an emergency halt to the system using NedHAL_EmergencyStop which is usually undesirable.

Finally, for the benefit of code compiled with stack-checking enabled, the APCS stack limit register (sl=R10) is set to current stack less current stack size. This should catch any massive overflows of stack space, but as the routine cannot know the base of the stack for efficiency reasons, it may allow for corruption of memory. Besides, the APCS stack extending routine can not extend a supervisor stack so enabling stack checking on handler code is wasteful. Hence, we recommend the use of #pragma nocheck_stack around C functions which act as exception handlers.

Example handler code:

APCS C ARM assembler
#pragma nocheck_stack

extern HALError *Handler(int param)
{
  ...perform handling code ...
   if(nothandled) return (HALError *) 0;
   if(handled) return (HALError *) 1;
   if(error) return errblk;
}

#pragma check_stack
- -         EXPORT ExampleHandler ...
ExampleHandler
       STMFD R13!,{R1-R3, ..., R14}
        ... perform handling code ...
        CMP Rx,#nothandled
        MOVEQ R0,#0
        CMP Rx,#handled
        MOVEQ R0,#1
        CMP Rx,#error
        LDREQ R0,=errorblkptr
        LDMFD R13!,{R1-R3, ..., PC}

 

ExceptH_Initialise

Purpose - - Initialises the exception handling module for subsequent use
Entry None
Exit R0 = Null if no error occurred, pointer to HALError otherwise
Interrupts IRQ is disabled
FIQ is unchanged
Processor Mode SVC32 if User32 on entry, otherwise unchanged
Staticity Alters zero page memory only
Use None
Notes None

 

ExceptH_Finalise

Purpose     Deinitialises the exception handling module
Entry None
Exit R0 = Null if no error occurred, pointer to HALError otherwise
Interrupts IRQ is disabled
FIQ is unchanged
Processor Mode SVC32 if User32 on entry, otherwise unchanged
Staticity Alters zero page memory only
Use None
Notes None

 

ExceptH_AddHandler

Purpose     Adds a handler for a particular vector
Entry R0 = ARM vector number
R1 = address of handler to add
R2 = value to pass to handler (if bit 2 of R3 set then use is reserved, use zero)
R3 = bit 0:  Place handler at top of list (against bottom otherwise)
bit 1:  Pass R2 in R12 rather than R0
bit 2:  This is a former handler of this vector
bits 3-31:  Reserved for future use
Exit R0 = Null if no error occurred, pointer to HALError otherwise
Interrupts IRQ is disabled
FIQ is unchanged
Processor Mode SVC32 if User32 on entry, otherwise unchanged
Staticity Alters zero page memory only
Use The ARM vector number specified by R0 is determined by ResetV=0, UndefV=1, SWIV=2, PAbortV=3, DAbortV=4, IRQV=6, FIQV=7 - the same way the ARM processor has them. There are macros equivalent to these names defined in BoardDefines for your convenience.

Placing a handler at the top of the calling list (bit 0 set) requires the list to be shifted downwards in memory first - this imposes an additional interrupt latency as interrupts are disabled during the duration of the move. Note that bit 0 is forced set on all non-STANDALONE builds as one always wishes for handlers to be added before the jump to the debugger - see the section below about bit 2/

Bit 1 is intended to be used when you wish R0-R7 to be preserved on entry to the handler from when the exception happened. This most likely occurs in the SWI exception handler when current register contents are used as parameters for the SWI call. If you wish to write the SWI handler in C, either use parameters which don't include R0 (easy as R0 usually contains the error status on SWI exit) or else write a function wrapper in assembler which moves R12 into a more convenient register or memory location. You could even embed assembler into the start of the function on some ARM compilers to retrieve R12 with the least efficiency loss.

The use of bit 2 to add a former handler of this vector carries with it a number of caveats. Firstly, it will always claim any exceptions passed to it - if you don't want this, add it after you have added all your handlers or else add all handers with bit 0 set. You do not need to do this versions of the HAL built without the STANDALONE macro defined as bit 0 is always forced on on DEMON or ANGEL builds as these always install the Demon or Angel former handler during startup.

The use of bit 2 handlers also adds a considerable latency to the execution of that handler. After all the ExceptH listed handlers preceding its entry have been called, ExceptH restores pre-exception context completely before it calls the former handler (hence appearing as though ExceptH had never been called). This requires considerable stack manipulation, and hence many cycles are used.

Notes The following values in BoardDefines determine how many handlers exist for each ARM vector:
  • UndefinedExcptHandlers
  • SWIExcptHandlers
  • PAbortExcptHandlers
  • DAbortExcptHandlers
  • IRQExcptHandlers
  • FIQExcptHandlers

 

ExceptH_RemoveHandler

Purpose     Removes a handler for a particular vector
Entry R0 = ARM vector number
R1 = address of handler
Exit R0 = Null if no error occurred, pointer to HALError otherwise
Interrupts IRQ is disabled
FIQ is unchanged
Processor Mode SVC32 if User32 on entry, otherwise unchanged
Staticity Alters zero page memory only
Use The ARM vector number is determined by ResetV=0, UndefV=1, SWIV=2, PAbortV=3, DAbortV=4, IRQV=6, FIQV=7 - the same way the ARM processor has them. There are macros equivalent to these names defined in BoardDefines for your convenience.

The removal of a handler at the top of the list will incur greater interrupt latency than one at the end of the list as the list onwards from the handler's entry must be copied upwards.

Notes None