Writing interrupt handlers for ARM

Description
armcc provides the '__irq' declaration keyword to allow writing interrupt handlers in C. However this was only designed for 'simple' (non re-entrant) interrupt handlers. This is because it does not store all of the state information required for re-entrant interrupts.

 

The level of complexity of your 'top-level' interrupt handler depends on whether it needs to call subroutines (with the BL instruction) and whether it needs to be re-entrant. In a typical system, there may be several interrupt sources, with different priorities, connected via IRQ. A re-entrant interrupt handler would allow e.g. a higher priority IRQ to interrupt the processing of a lower-priority IRQ. A re-entrant interrupt handler (at least its 'top-level') *must* be written in assembler.

Solution
To allow subroutines to be called from the top-level handler, the LR_IRQ must be saved (e.g. stacked) before the BL call and restored afterwards, otherwise the BL will corrupt the 'return address' of the interrupt handler by overwriting it with the return address of the subroutine. Additionally, registers r0-r3 and r12 are not preserved by the called function (as specified by the APCS), so must be preserved by the caller.

The __irq function handles all these details, for example,

__irq void IRQHandler (void)
{
volatile unsigned int *base = (unsigned int *) 0x80000000;
if (*base == 1) // which interrupt was it?
{
C_int_handler(); // process the interrupt
}
*(base+1) = *base; // clear the interrupt
}

compiled with armcc gives:

IRQHandler
0x000000: STMFD sp!,{r0-r4,r12,lr}
0x000004: MOV r4,#0x80000000
0x000008: LDR r0,[r4,#0]
0x00000c: CMP r0,#1
0x000010: BLEQ C_int_handler
0x000014: LDR r0,[r4,#0]
0x000018: STR r0,[r4,#4]
0x00001c: LDMFD sp!,{r0-r4,r12,lr}
0x000020: SUBS pc,lr,#4

It is important to clear the source of the interrupt somewhere within the interrupt handler, otherwise the current pending interrupt will be taken again immediately after returning from the interrupt handler, creating an endless loop. This is typically done by accessing an 'interrupt acknowledged' register in the interrupt controller hardware.

The IRQHandler C function above must be compiled with armcc, however, C_int_handler() may be a C function compiled for ARM or Thumb. The linker can add any necessary ARM/Thumb interworking veneers to perform the change of state.

Re-entrant interrupt handlers, however, are rather more complex.

If an interrupt handler re-enables interrupt, and another interrupt occurs during execution of a BL instruction, then the return address of the subroutine (stored in LR_IRQ) WILL BE CORRUPTED when the 2nd IRQ exception is taken.

Before the BL to the subroutine/C function, you should switch to another mode, typically System mode (supported in architecture 4 onwards, e.g. in ARM7TDMI) by changing the CPSR, so that the User mode registers are used with privileged protection, then immediately push the User mode LR on the User mode stack. It is then safe to call the subroutine with BL. On return you can restore the User mode LR, switch back to IRQ mode and leave as normal. For more information on System mode see SDT 2.11 User Guide, section 11.13, or SDT 2.50 User Guide, section 9.12.

Again, it is important to clear the source of the interrupt before re-enabling interrupts, otherwise the current pending interrupt will be taken again immediately after re-enabling interrupts, creating an endless loop.

On entry:

  • save (adjusted) LR and SPSR
  • clear the source of the interrupt
  • switch to System mode and re-enable core interrupts
  • save more registers if needed
  • call C interrupt dispatch function
  • switch back to IRQ mode and disable interrupts
  • restore SPSR
  • return

This is best implemented in ARM assembler. Try this:

    AREA INTERRUPT, CODE, READONLY
IMPORT C_irq_handler
IRQ
SUB lr, lr, #4
STMFD sp!, {lr} ; save adjusted LR_IRQ

MRS r14, SPSR
STMFD sp!, {r12, r14} ; save workreg & SPSR_IRQ
 
           ; add instructions to clear the interrupt here
    MSR    CPSR_c, #0x1F    ; go to System mode, IRQ & FIQ enabled

    STMFD  sp!, {r0-r3, lr} ; save LR_USR and non-callee saved registers
BL C_irq_handler ; call C irq handler
LDMFD sp!, {r0-r3, lr} ; restore
 
    MSR    CPSR_c, #0x92    ; go back to IRQ mode, disable IRQ (FIQ still enabled)
    LDMFD  sp!, {r12, r14}  ; restore workreg & SPSR_IRQ
MSR SPSR_cf, r14
LDMFD sp!, {PC}^ ; and return
    END

Again, C_irq_handler() may be a C function compiled for ARM or Thumb. The linker can add any necessary ARM/Thumb interworking veneers to perform the change of state.
As an additional note, please be aware that if an interrupt occurs at the same time when the interrupt is disabled by the program, the ARM7 family may not behave as expected. To allow for this, additional instructions are needed at the start of the interrupt handler. See 
  .

In the above example, please note:

  • r12r14 are being used as temporary work regs, so have been written as 'r12' & 'r14' for clarity instead of 'ip' & 'lr' to reflect their alternate use.
  • You must add instructions to clear the interrupt, before re-enabling the interrupt. r12 & r14 are available as work registers for this, having already been stacked.
  • FIQ remains enabled, so that a (higher-priority) FIQ can interrupt an IRQ. If your system does not use FIQ, or does not require FIQ to interrupt an IRQ, then FIQs should be disabled.
  • For pre-architecture 4 ARMs (which do not have System mode) switch to Supervisor mode instead (load CPSR_c with 0x13 instead of 0x1F).

 

original article: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka3709.html

 

Another implementation of nesting interrupt for example:

 

PRESERVE8
    AREA Interrupt, CODE, READONLY
    CODE32    ; ARM code 


;---------------------------------------------------------------------
;- Function            : IRQ_ISO7816_RxD_handler_asm
;- Called Functions    :
;- Functions Brief     : netsting interrupt handler
;--------------------------------------------------------------------- 
    EXPORT   IRQ_ISO7816_RxD_handler_asm
    IMPORT   IRQ_ISO7816_RxD_handler_C 

IRQ_ISO7816_RxD_handler_asm 
; Adjust and save the LR_irq in the current stack
     SUB  LR, LR, #4
     STMFD  SP!, { LR }

 

; Save the SPSR_irq and R0 in the current stack
     MRS R14, SPSR
     STMFD SP!, { R0, R14 }

 

; Switch into the System mode and re-enable the IRQ interrupt
     MRS R0, CPSR
     BIC R0, R0, #0x80
     ORR R0, R0, #0x1F 
     MSR CPSR_c, R0 

 

; Save the AAPCS rules registers and LR in the System mode stack
     STMFD SP!, { R1- R3, R12, R14 }
     

; Branch to C IRQ handler
     BL IRQ_ISO7816_RxD_handler_C

 

; Restore the AAPCS rules registers and LR from System mode stack
     LDMFD SP!, { R1- R3, R12, R14 }

 

; Switch back to the IRQ mode and disable the IRQ interrupt
     MRS R0, CPSR
     BIC R0, R0, #0x1F
     ORR R0, R0, #0x92 
     MSR CPSR_c, R0  
 
; Restore the SPSR_irq and R0 from the IRQ stack
     LDMFD SP!, { R0, R14 }
     MSR SPSR_cxsf, R14
 
; Restore the LR_irq and return
     LDMFD SP!, {PC}^

 

 

///c handler

extern void IRQ_ISO7816_RxD_handler_asm(void);

void IRQ_ISO7816_RxD_handler_C(void)
{

}

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章