ISR only stack - POC stage 1

alunz wrote on Tuesday, February 15, 2005:

Following is some code that is offered as a Proof of Concept of a ISR only stack.  (The code includes a sample ISR which demonstrates how to use it.) The code is targeted for a MSP430F149 and is obviously not portable in the slightest; it is offered with the usual disclaimers (if it breaks your application/computer - you get to keep the pieces).

Having said that, it does seem to work, but has not been tested thoroughly.  Specifically; whether it works with nested interrupts has yet to be tested.

The other limitations of this are:
- It is NOT suitable for wrapping the timer ISR for pre-emptive scheduling (the reason why is left as an exercise for the reader).  Some different code will have to be produced to support that.
- Not sure if the approach is all that portable.
- The wrapper macro is does not qualify as elegant at all.  There are probably smart ways to determine main’s top of stack, and the address range for allocated stack space.  But have yet to figure out how to do that.
- The whole wrapper macro approach was adopted as there does not seem to be a generic way to determine a functions register / stack usage.  This approach work, but does have the overhead of an extra function call (5 cycles).

Next up is to figure out a version which will support the timer tick for pre-emptive scheduling.  Am open to any comments / suggestions?



* ISR stack shenanigan routines
volatile unsigned portSHORT usIsrToYield = 0;

// The maximum value for SP when in normal application space:
// 0x09c0 == 2496
#define MAX_STACK_ADDR      "2496"

// The top of the ISR stack:
// 0x0a00 == 2560
#define ISR_STACK_SAVED     "2560"

#define INTERRUPT_FUNCTION(irq, fcn)
  interrupt ( (irq) ) fcn ## _wrapper ( void ) __attribute__ ( ( naked ) );
  interrupt ( (irq) ) fcn ## _wrapper ( void ) 
  asm volatile ( 
          "cmp #" MAX_STACK_ADDR ", r1    \n\t"
          "jc $+10            \n\t"
          "push r15           \n\t"
          "mov.w r1, r15      \n\t"
          "mov.w #" ISR_STACK_SAVED ", r1  \n\t"
          "push r15           \n\t"
          "push r14           \n\t"
          "push r13           \n\t"
          "push r12           \n\t"
          "call #" #fcn "     \n\t"
          "pop r12            \n\t"
          "pop r13            \n\t"
          "pop r14            \n\t"
          "pop r15            \n\t"
          "cmp #" ISR_STACK_SAVED ", r1    \n\t"
          "jnc $+16           \n\t"
          "mov.w r15, r1      \n\t"
          "pop r15            \n\t"
          "cmp #0, &usIsrToYield \n\t"
          "jz $+6             \n\t"
          "call #vPortYield   \n\t"
          "reti               \n\t"
#define INTERRUPT_TASK_YIELD  ( usIsrToYield = 1 )


/* declare the routines that service the interrupt, wrapping them
* in the ISR stack handler.
static void vRxIsr( void );
static void vRxIsr( void );



* UART RX interrupt service routine.
static void vRxISR( void )
  signed portCHAR cChar;
  /* Get the character from the UART and post it on the queue of Rxed
  characters. */
  cChar = U0RXBUF;
  if( cQueueSendFromISR( xRxedChars, &cChar, pdFALSE ) )
    /*If the post causes a task to wake force a context switch
    as the woken task may have a higher priority than the task we have
    interrupted. */

alunz wrote on Tuesday, February 15, 2005:

*sigh*  why is it that the the obvious solutions are only spotted after posting something to the world?  The obvious way to use this new approach with the timer ISR is:

  static void prvTickISR( void )
        /* Increment the tick count then switch to the highest priority task
        that is ready to run. */

Think that this could be optimised even further to save a byte or two off each tasks stack.

Oh, the INTERRUPT_FUNCTION macro has a “deliberate” mistake in it.  The last `jz’ line should be replaced with:
          “jz $+10            \n\t”
          “mov #0, &usIsrToYield \n\t” \

Using this approach have managed to save about 26 bytes per task.  In the EW2 demo this means that after reducing the min. stack size there is now enough memory to add the integer test tasks (another 2 tasks). 

So far so good.  Now what to do…?


rtel wrote on Tuesday, February 15, 2005:

I will have to have a closer look at these to understand their operation properly.

alunz wrote on Tuesday, February 15, 2005:

Sorry for the lack of comments.  After thinking about this for so long that the brain hurts aLUNZ forgot that anyone else may not be able to quickly spot what the code is meant to do.  Of course, aLUNZ knows better than this, but just wanted to get the thing out the door; which is no excuse really.

A quick explanation:
The macro defines a "naked" function that wraps the routine that actualy does the work of the ISR.  The purpose of the wrapper function is to replace the normal (compiler provided) preamble and postamble with routines that switch the stack. 

The wrapper function only has to save/recover the "call destroyed" (r15 to r12) registers as the worker routine pre/postamble will preserve any other registers and stack space used.

- On entry the routine tests the SP to see if we are already using the ISR stack space, if not the current SP is saved and then set to the top of what used to be main’s stack. 
- Registers 15 to 12 are then pushed onto the stack.
- The `worker’ function is then called.  The worker function can choose to initiate a task switch by calling INTERRUPT_TASK_YIELD which sets a flag to indicate that a task switch is requested.
- When the worker routine returns, registers 12 to 15 are popped off the stack.
- If the SP is at the top of the stack area, the original SP (pointing into a tasks stack space) is loaded.  At this point the registers (with the exception of the PC and SR) are as they were at ISR entry; reti can now be called safely.
- If the usIsrToYield is non-0 the routine will call TaskYIELD().
- The routine then calls reti to return.