ARM Nesting of Interrupts

NESTING INTERRUPTS

Applies to: RealView C Compiler

Answer


Information in this article applies to:
  • RealView Compiler Version 3.0 or higher

QUESTION

The classic ARM architecture only provides two interrupts (IRQ and FIQ). The Vectored Interrupt Controller or Advanced Interrupt Controller provides interrupt priorities and interrupt nesting for the standard interrupt, but it requires that you set the I bit in the CPSR.
What is the best method to allow interrupt nesting with the RealView compiler?

ANSWER

It should be noted that good programming technique implies that you keep interrupt functions very short. When you are using short interrupt functions, interrupt nesting becomes unimportant. When you are using an Real-Time Operating System (such as the RTX Kernel), the stack usage of user tasks becomes unpredictable when you allow interrupt nesting.
However, if you still need interrupt nesting in your application, you may implement it using an assembly language wrapper function as described below:
Example:
Within the following __irq function you want to allow interrupt nesting:
void eint1_irq (void)  __irq  {   // C interrupt function
  EXTINT      = 2;                // Clear EINT1 interrupt flag
  delay ();                       // wait a long time
  ++intrp_count;                  // increment interrupt count
  VICVectAddr = 0;                // Acknowledge Interrupt
}
Change the interrupt function as shown below:
extern void eint1_irq (void);     // wrapper ASM interrupt function

void eint1_srv (void)  {          // C interrupt code
  EXTINT      = 2;                // Clear EINT1 interrupt flag
  delay ();                       // wait a long time
  ++intrp_count;                  // increment interrupt count
//VICVectAddr = 0;                // Acknowledge Interrupt
}
Add the following assembler wrapper (in a separate assembly module) to the interrupt function:
       PRESERVE8
       AREA    NEST_IRQ, CODE, READONLY
       ARM
       IMPORT  eint1_srv
       EXPORT  eint1_irq

eint1_irq

       PUSH    {R0-R3,R12,LR}          ; save register context
       MRS     LR, SPSR                ; Copy SPSR_irq to LR
       PUSH    {LR}                    ; Save SPSR_irq
       MSR     CPSR_c, #0x1F           ; Enable IRQ (Sys Mode)
       PUSH    {LR}                    ; Save LR

       BL      eint1_srv

       POP     {LR}                    ; Restore LR
       MSR     CPSR_c, #0x92           ; Disable IRQ (IRQ Mode)
       POP     {LR}                    ; Restore SPSR_irq to LR
       MSR     SPSR_cxsf, LR           ; Copy LR to SPSR_irq

;      VICVectAddr = 0;                ; Acknowledge Interrupt
       MOV     R0,#0
       STR     R0,[R0,#-0xFD0]

       POP     {R0-R3,R12,LR}          ; restore register context
       SUBS    R15,R14,#0x0004         ; return from interrupt


       END
Note:
  • The example above shows interrupt nesting with the NXP LPC2000 devices. It is important that you move the interrupt acknowledge sequence VICVectAddr = 0; to the assembly wrapper. Other ARM devices may require a different interrupt acknowledge sequence. Therefore consult the user's manual of the ARM device that you are using.

Another good approach  from source:
     Many a times it would be handy if we could let the interrupt handling routine decide whether to nest an interrupt or not. Yes this voilates the concept of interrupt prioritization but there might be times say a data frame sync fault has occurred and you would probably need to restart the transfer and which may take a much longer time. In such situations your device driver might decide to allow other interrupts to occur. This can be accomplished in two ways
  1. To raise a Software Interrupt to do this switch
  2. To call an assembly routine that takes care of the context switching

Software Interrupt approach:

      This is a very good approach a standardized system routine to take care of this. But is it really necessary to do all this in a simple embedded application. This is a good approach for any high end operating systems. The routine may decide upon whether to allow the driver to nest the interrupt and wait or should it return immediately. I’m not posting more on this here as this not the simplest of approach or easy to give it for free..:P
A typical Software Interrupt Approach needs to be implemented in the following way or something similar
  1. Create a common IRQ distribution routine
  2. IRQ distribution routine will call the actual interrupt handler
  3. Handler can raise a SWI for allowing interrupt nesting
  4. Handler shall return to the IRQ distribution routine
  5. IRQ distribution routine safely resumes the previous process

Assembly Routine:

    By writing an embedded assembler routing supported by ARMGCC compiler the developer has the flexibility to do an interrupt nesting by simple calling this routine(Just like a function call). You would need to create two embedded assembler routines
  1. To Enable the processor interrupt and switch to system mode.
  2. To Disable the processor interrupt and switch back to the original program.
Before we look at the embedded assemblers used to serve this purpose let us see how parameters are passed to embedded assemblers.
__asm void enableIRQ(int dummy_variable)
The dummy variable is available at the register R0. As I mentioned earlier the call to this embedded assembler is just like any function call, on return the old value of the register R0 will be restored. This behavior is opposite to that of a normal function call in which the called function takes care of storing and restoring the registers. This is completely a compiler dependent behavior. If you are interested take a look at the disassemble.
The complete IRQ Nesting code use in SRMSAT-1

__asm void enableIRQ(int dummy)
{
MRS r0, SPSR
STMDB SP!,{r0}
MOV r0,LR
MSR CPSR_c, #0x9F
STMDB SP!,{LR}
MOV LR,r0
MSR CPSR_c,#0x1F
MOV PC,LR
}
__asm void disableIRQ(int dummy)
{
MSR CPSR_c, #0x9F
MOV r0,LR
LDMIA SP!, {LR}
MSR CPSR_c, #0x92
LDMIA SP!, {LR}
MSR SPSR_cxsf, LR
MOV PC,r0
}
I have forced the compiler to free the contents of the register R0 by passing an useless parameter to create an useful slot for swapping the register contents. I’am assuming that the reader is familiar with ARM assembly. If you have more doubts read my other posts on ARM instruction set(At this time i’m still editing my blog on instruction set as it is too big or if I should make it just contain references to someone else blog as there are already a lot of available resources on ARM instruction set and execution). It is a must for the developer to call the “disableIRQ” before returning from the interrupt handler. Else you are going to corrupt the stack and bread the code.
Now before I finish let us see the advantages and drawbacks of both the approaches and which approach you should choose for your application.

Advantages
System IRQ Approach

  1. OS can can decide whether to allow or not.
  2. Switching can be made to “User” mode instead of “System” mode and leading to a
  3. security vulnerability.
  4. OS can consider dynamic system conditions to decide.

Embedded Assembler Approach

  1. Light weight
  2. Easy to use with any Embedded C/C++ projects

Drawbacks
System IRQ Approach

  1. Not all embedded applications have a proper OS.
  2. Adds a lot of procedure to follow in implementing, for instance after the call we need to examine whether the access was granted or not.

Embedded Assembler Approach

  1. Highly vulnerable to security issues due to the switch to privileged system mode.

Choosing the right approach for your application:

Unless you are developing a competing OS for Symbian or Windows do not go for the writing the system IRQ routines(To know about writing System IRQ’s read my post on “Software Interrupts”). If you are assigned a task like designing the On-Board computer system for a satellite like me go for the Embedded Assembler. If you are making an ATM machine then do not go for Embedded Assemblers, in fact for a system like that i prefer to have no nesting.
In lay mans term, If you have a project which involves multiple developers and 3rd party developers write a System IRQ routine (Software Interrupt) else use this simple embedded assembler.


Comments

Popular posts from this blog

Airtel Digital tv remote factory reset and reprogram

Tracking Linux kworker threads

Copy_to_user or copy_from_user instead of memcpy