Interrupts and task switches in the ARM7

jorick23 wrote on Thursday, June 26, 2008:

Processor: STR750 (ARM7)
Compiler:  IAR

I just saw a post asking about interrupt wrappers for the ARM7 so I thought I’d respond with this post.  Since it doesn’t really answer his question, I’m making it a new thread.

I’ve set up my interrupts to be normal functions running in Thumb mode.  They’re called from an interrupt handler that takes care of everything: saving and restoring task contexts, resetting the interrupt pending bits, etc.  The IRQ handler even contains the SWI handler.  In order to speed things up (30% less execution time) and reduce stack usage, I have the RTOS store the task context in the Task Control Block instead of on the stack.  There was a previous post to this board about how to do that so I took the code and optimized it.

Here is the interrupt function:

*~    _Vector_SWI                Software Interrupt exception vector          *
*~    _Vector_IRQ                Interrupt exception vector                   *
*~    vPortStartFirstTask        Start the first task                         *
*                                                                             *
* This function handles exceptions.  The task state is saved prior to calling *
* the exception handler and restored upon completion.  During the exception,  *
* it’s possible for the task context to change if a task was awakened by the  *
* exception handler.                                                          *
*                                                                             *
* Normally, ARM interrupts on the STR750 are handled by branching to the      *
* Interrupt Vector Register which holds a previously stored “ldr pc, #offset” *
* instruction, and the offset was read and set from a table.  This requires   *
* interrupt functions to be in ARM mode.  However, some interrupt functions   *
* in the pump software were written in Thumb mode so this method of handling  *
* interrupts won’t work.  So instead of an instruction, interrupt vectors are *
* read from a table in ROM.                                                   *
*                                                                             *
*       Args:   void                                                          *
*                                                                             *
*       Return: void                                                          *
*                                                                             *

// ***** SWI exception handler ************************************************


// The return address is bumped by 4 to make it look like the context was saved
// during an IRQ handler.

        add     lr, lr, #4              // Adjust the link register

// ***** IRQ exception handler ************************************************


// ***** Save the task context
// The task context is saved in the task control block.  If the task is changed
// during the interrupt, the task context restored at the end of this function
// will be different.  First, the r0 register is saved and used to read the
// current TCB pointer.  The registers r1 through r14 are saved in the TCB.

        stmdb   sp!, {r0}               // Save r0 for use as scratch register
        ldr     r0, =pxCurrentTCB       // Point to the current TCB pointer
        ldr     r0, [r0]                // Get it
        add     r0, r0, #16             // Find the position of the r1 register
        stmia   r0, {r1-r14}^           // Save the task context

// The critical nesting depth, status register, link register, and saved r0
// register are loaded into registers and saved in the task control block.

        ldr     r1, =ulCriticalNesting  // Point to the critical nesting depth
        ldr     r1, [r1]                // Get it
        mrs     r2, spsr                // Get the status register
        mov     r3, lr                  // Get the link register
        ldmia   sp!, {r4}               // Get the saved r0 register
        sub     r0, r0, #16             // Find the start of the context frame
        stmia   r0, {r1-r4}             // Save the rest of the task context

// ***** SWI: Switch contexts
// If this function was called from _Vector_SWI (the SWI handler), it switches
// contexts.  Otherwise, if this function was called because of a pending
// interrupt, the interrupt is handled.

        mrs     r0, cpsr                // Get the condition codes
        tst     r0, #1                  // Check if SWI or IRQ
        beq     _IrqHandler             // If IRQ, go handle the interrupt
        ldr     r0, =vTaskSwitchContext // Point to the task switcher
        mov     lr, pc                  // Set up the return address
        bx      r0                      // Switch task contexts
        b       vPortStartFirstTask     // Go restore the task context

// ***** IRQ: Handle the interrupt
// If this is an interrupt, the interrupting channel is read and used to index
// into the vector table to find the address of the handler, which is then
// called.

        ldr     r0, =EIC                // Point to the EIC registers
        ldr     r1, [r0, #EIC_IVR]      // Update them (dummy read)
        ldr     r0, [r0, #EIC_CICR]     // Get the interrupting channel number
        mov     r0, r0, lsl #2          // Convert it to a table index
        ldr     r1, =_IrqVectorTable    // Point to the vector table
        ldr     r0, [r1, +r0]           // Get the handler address
        mov     lr, pc                  // Set up the return address
        bx      r0                      // Call the appropriate function

// After the interrupt has been handled, the interrupting channel is read and
// used to clear the pending flag.

        ldr     r0, =EIC                // Point to the EIC registers
        ldr     r1, [r0, #EIC_CICR]     // Get the interrupting channel number
        mov     r2, #1                  // Set up a flag bit
        mov     r2, r2, lsl r1          // Position it in the word
        str     r2, [r0, #EIC_IPR]      // Clear the interrupt pending flag

// ***** Restore the task context
// This part of the exception handler also starts the first task by restoring
// the task context created by the task scheduler.


// The critical nesting depth, status register, and link register are restored.

        load    r0, pxCurrentTCB        // Get the current TCB pointer
        ldmia   r0!, {r1-r2, lr}        // Read the values
        ldr     r3, =ulCriticalNesting  // Point to the critical nesting depth
        str     r1, [r3]                // Restore it
        msr     spsr_cxsf, r2           // Restore the status register

// The task’s system mode registers are restored.

        ldmia   r0, {r0-r14}^           // Restore the user mode registers
        nop                             // [Banked register access insurance]

// The function returns to the task.  This might not be the same task that was
// interrupted, if the task context was changed.

        subs    pc, lr, #4              // Return from the interrupt

// ***** Interrupt (EIC) vector table

        dc32    _Vector_SpuriousIrq     //  0: Wakeup
        dc32    _Vector_SpuriousIrq     //  1: TIM2 Output Compare 2 (not used)
        dc32    _Vector_SpuriousIrq     //  2: TIM2 Output Compare 1 (not used)
        dc32    _Vector_SpuriousIrq     //  3: TIM2 Input Capture (not used)
        dc32    _Vector_SpuriousIrq     //  4: TIM2 Update (not used)
        dc32    _Vector_SpuriousIrq     //  5: TIM1 Output Compare 2 (not used)
        dc32    _Vector_SpuriousIrq     //  6: TIM1 Output Compare 1 (not used)
        dc32    _Vector_SpuriousIrq     //  7: TIM1 Input Capture (not used)
        dc32    _Vector_SpuriousIrq     //  8: TIM1 Update (not used)
        dc32    _Vector_SpuriousIrq     //  9: TIM0 Output Compare 2 (not used)
        dc32    _Vector_SpuriousIrq     // 10: TIM0 Output Compare 1 (not used)
        dc32    _Vector_SpuriousIrq     // 11: TIM0 Input Capture (not used)
        dc32    _Vector_SpuriousIrq     // 12: TIM0 Update (not used)
        dc32    _Vector_SpuriousIrq     // 13: PWM Output Compare (not used)
        dc32    _Vector_SpuriousIrq     // 14: PWM Timer Emergency (not used)
        dc32    _Vector_SpuriousIrq     // 15: PWM Timer Update (not used)
        dc32    I2C_Irq                 // 16: I2C
        dc32    _Vector_SpuriousIrq     // 17: SSP1 (not used)
        dc32    _Vector_SpuriousIrq     // 18: SSP0 (not used)
        dc32    Keypad_Irq_Touch        // 19: UART 2 (Touchscreen interface)
        dc32    Serial_Rs485_Irq        // 20: UART 1 (RS485)
        dc32    Serial_RS232_Irq        // 21: UART 0 (RS232)
        dc32    _Vector_SpuriousIrq     // 22: CAN (not used)
        dc32    USB_Istr                // 23: Low priority USB
        dc32    CTR_HP                  // 24: High priority USB
        dc32    ADC_Irq                 // 25: Analog to Digital Converter
        dc32    _Vector_SpuriousIrq     // 26: DMA (not used)
        dc32    _Vector_EXTIT           // 27: External interrupts
        dc32    _Vector_SpuriousIrq     // 28: MRCC (not used)
        dc32    _Vector_SpuriousIrq     // 29: FLASHSMI (not used)
        dc32    _Vector_SpuriousIrq     // 30: Real Time Clock (not used)
        dc32    vPortPreemptiveTick     // 31: Time base timer

// ***** Spurious interrupt
// The registers are set as follows:
//    r0 = EIC register pointer
//    r1 = IRQ vector table pointer
//    r2 = spsr
//    r3 = Return address to the point just after the interrupt occurred
//    r4-r12 are undefined

        sub     lr, lr, #4              // Point to the interrupted instruction
        b       .                       // SPURIOUS INTERRUPT TRAP

In the TCB, I added one line right at the beginning, so it now reads:

typedef struct tskTaskControlBlock
   portSTACK_TYPE       pxCpuContextFrame[ portCPU_CONTEXT_SIZE ];
   volatile portSTACK_TYPE *pxTopOfStack;    /*< Points to the location of the last item placed on the tasks stack.>*/

The pxPortInitialiseStack function has been changed to:

portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *ctx, portSTACK_TYPE *pxTopOfStack, pdTASK_CODE pxCode, void *pvParameters )

   /* Interrupt flags cannot always be stored on the stack and will
   instead be stored in a variable, which is then saved as part of the
   tasks context. */
   *ctx = portNO_CRITICAL_NESTING;

   /* The status register is set for system mode, with interrupts enabled. */
   *ctx = ( portSTACK_TYPE ) portINITIAL_SPSR;

   /* The return address - which in this case is the start of the task.  The
   offset is added to make the return address appear as it would within an
   IRQ ISR.*/
   *ctx = ( portSTACK_TYPE ) pxCode + portINSTRUCTION_SIZE;

   /* When the task starts is will expect to find the function parameter in
   R0. */
   *ctx = ( portSTACK_TYPE ) pvParameters; /* R0 */
   *ctx = ( portSTACK_TYPE ) 0x01010101;   /* R1 */
   *ctx = ( portSTACK_TYPE ) 0x02020202;   /* R2 */
   *ctx = ( portSTACK_TYPE ) 0x03030303;   /* R3 */
   *ctx = ( portSTACK_TYPE ) 0x04040404;   /* R4 */
   *ctx = ( portSTACK_TYPE ) 0x05050505;   /* R5 */
   *ctx = ( portSTACK_TYPE ) 0x06060606;   /* R6 */
   *ctx = ( portSTACK_TYPE ) 0x07070707;   /* R7 */
   *ctx = ( portSTACK_TYPE ) 0x08080808;   /* R8 */
   *ctx = ( portSTACK_TYPE ) 0x09090909;   /* R9 */
   *ctx = ( portSTACK_TYPE ) 0x10101010;   /* R10 */
   *ctx = ( portSTACK_TYPE ) 0x11111111;   /* R11 */
   *ctx = ( portSTACK_TYPE ) 0x12121212;   /* R12 */
   *ctx = ( portSTACK_TYPE ) pxTopOfStack; /* Stack used when task starts goes in R13. */
   *ctx = ( portSTACK_TYPE ) 0xaaaaaaaa;   /* R14 */
   return pxTopOfStack;

Since there’s an extra parameter in pxPortInitialiseStack, the call in Task.c needs to change to:

      pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxNewTCB->pxCpuContextFrame, pxTopOfStack, pvTaskCode, pvParameters );

An extra bonus is that my interrupt functions can now be located anywhere in memory, not just the first 64K as is the case when using the interrupt vector register.

davedoors wrote on Thursday, June 26, 2008:

Cool. The ARM7 port is now one of the older ports maybe could do with a revamp. The PIC32 and Cortex ports (which are newer) are more featured. Thoughts Richard?