Forums

Questions aboutreentrant interrupts on ARMv7

Started by Marek Omama February 22, 2012

I am trying to figure out how reentrant interrupts are supposed to
work on ARMv7. According to the ARM document DUI0203H, this is what
you have to do:

Example 6.16. Nested Interrupt (ARMv6, non-vectored interrupts)
IRQ_Handler
    SUB lr, lr, #4
    SRSFD #0x1f!               ; Save LR_irq and SPSR_irq to System
mode stack
    CPS #0x1f                      ; Switch to System mode
    STMFD sp!, {r0-r3,r12}         ; Store other AAPCS registers
    AND r1, sp, #4
    SUB sp, sp, r1
    STMFD sp!, {r1, lr}
    BL identify_and_clear_source   ; *** 1 ***
    CPSIE i                        ; Enable IRQ
    BL C_irq_handler        ; *** 2 ***
    CPSID i                        ; Disable IRQ
    LDMFD sp!, {r1,lr}
    ADD sp, sp, r1
    LDMFD sp!, {r0-r3, r12}        ; Restore registers
    RFEFD sp!                      ; Return using RFE from System mode
stack

The problem I see is that the interrupt usually needs to be
acknowledged and cleared at two locations, once for the source of the
interrupt and once for the interrupt controller and in that order. The
snippet below (from ARM Knowledge Base on nested interrupts)
illustrates this on the first and last lines:


/* External Interrupt 1 Service */
void eint1_srv (void) __irq {
  EXTINT      = 2;                    // Clear EINT1 interrupt flag
  IENABLE;                            // allow nested interrupts
  delay ();
  ++intrp_count;                      // increment interrupt count
  IDISABLE;                           // disable interrupt nesting
  VICVectAddr = 0;                    // Acknowledge Interrupt
}

So going back to the first code,  exactly what happens in the
identify_and_clear_source() function? Does it clear both the interrupt
source module and the interrupt controller? Can this single function
really know about all interrupt sources and how you clear them (they
can be hundreds in a large soc)? Or do we have to implement one such
function for each interrupt (in which case, the name
identify_and_clear_source doesn't make sense).

If anyone could explain this to me, or point me to some snippets,
articles or literatures explaining this I would be very grateful.
In comp.arch.embedded,
Marek Omama <abc.junkaccount@gmail.com> wrote:
> > > I am trying to figure out how reentrant interrupts are supposed to > work on ARMv7. According to the ARM document DUI0203H, this is what > you have to do: > > Example 6.16. Nested Interrupt (ARMv6, non-vectored interrupts) > IRQ_Handler > SUB lr, lr, #4 > SRSFD #0x1f! ; Save LR_irq and SPSR_irq to System > mode stack > CPS #0x1f ; Switch to System mode > STMFD sp!, {r0-r3,r12} ; Store other AAPCS registers > AND r1, sp, #4 > SUB sp, sp, r1 > STMFD sp!, {r1, lr} > BL identify_and_clear_source ; *** 1 *** > CPSIE i ; Enable IRQ > BL C_irq_handler ; *** 2 *** > CPSID i ; Disable IRQ > LDMFD sp!, {r1,lr} > ADD sp, sp, r1 > LDMFD sp!, {r0-r3, r12} ; Restore registers > RFEFD sp! ; Return using RFE from System mode > stack > > The problem I see is that the interrupt usually needs to be > acknowledged and cleared at two locations, once for the source of the > interrupt and once for the interrupt controller and in that order. The > snippet below (from ARM Knowledge Base on nested interrupts) > illustrates this on the first and last lines: > > > /* External Interrupt 1 Service */ > void eint1_srv (void) __irq { > EXTINT = 2; // Clear EINT1 interrupt flag > IENABLE; // allow nested interrupts > delay (); > ++intrp_count; // increment interrupt count > IDISABLE; // disable interrupt nesting > VICVectAddr = 0; // Acknowledge Interrupt > } > > So going back to the first code, exactly what happens in the > identify_and_clear_source() function? Does it clear both the interrupt > source module and the interrupt controller? Can this single function > really know about all interrupt sources and how you clear them (they > can be hundreds in a large soc)? Or do we have to implement one such > function for each interrupt (in which case, the name > identify_and_clear_source doesn't make sense). > > If anyone could explain this to me, or point me to some snippets, > articles or literatures explaining this I would be very grateful.
It probably depends on the hardware you are using. This is how we did it for an AT91M55800 (ARM7TDMI) when we needed nested interrupts. One routine per interrupt source as it needs to clear the source as you said. Below is an example of one of the routines we used, but there is a similar one for each interrupt. The C IRQ handler does the actual work. Can't exactly remember where we found our example, but I guess it was from the Atmel examples. at91_tc2_irq_handler SUB lr, lr, #4 ; construct the return address STMFD sp!, {lr} ; and push the adjusted lr_IRQ MRS r14, SPSR ; copy spsr_IRQ to r14 STMFD sp!, {r12, r14} ; save work regs and spsr_IRQ ldr r12, = TCB0_BASE ; Clear RC compare status bit ldr r12, [r12, #TCC2_SR]; by reading TC_SR register MSR CPSR_c, #0x1F ; switch to SYS mode, FIQ and IRQ ; enabled. USR mode registers ; are now current. STMFD sp!, {r0-r3, lr} ; save lr_USR and non-callee ; saved registers BL C_at91_tc2_irq_handler ; branch to C IRQ handler. LDMFD sp!, {r0-r3, lr} ; restore registers MSR CPSR_c, #0x92 ; switch to IRQ mode and disable ; IRQs. FIQ is still enabled. ldr r12, = AIC_BASE ; mark the end of interrupt on the interrupt controller str r12, [r12, #AIC_EOICR] LDMFD sp!, {r12, r14} ; restore work regs and spsr_IRQ MSR SPSR_cf, r14 LDMFD sp!, {pc}^ ; return from IRQ. -- Stef (remove caps, dashes and .invalid from e-mail address to reply by mail) Just when you thought you were winning the rat race, along comes a faster rat!!
On 02/22/2012 09:00 PM, Marek Omama wrote:
> > > I am trying to figure out how reentrant interrupts are supposed to > work on ARMv7. According to the ARM document DUI0203H, this is what > you have to do: > > Example 6.16. Nested Interrupt (ARMv6, non-vectored interrupts) > IRQ_Handler > SUB lr, lr, #4 > SRSFD #0x1f! ; Save LR_irq and SPSR_irq to System > mode stack > CPS #0x1f ; Switch to System mode > STMFD sp!, {r0-r3,r12} ; Store other AAPCS registers > AND r1, sp, #4 > SUB sp, sp, r1 > STMFD sp!, {r1, lr} > BL identify_and_clear_source ; *** 1 *** > CPSIE i ; Enable IRQ > BL C_irq_handler ; *** 2 *** > CPSID i ; Disable IRQ > LDMFD sp!, {r1,lr} > ADD sp, sp, r1 > LDMFD sp!, {r0-r3, r12} ; Restore registers > RFEFD sp! ; Return using RFE from System mode > stack > > The problem I see is that the interrupt usually needs to be > acknowledged and cleared at two locations, once for the source of the > interrupt and once for the interrupt controller and in that order. The > snippet below (from ARM Knowledge Base on nested interrupts) > illustrates this on the first and last lines: > > > /* External Interrupt 1 Service */ > void eint1_srv (void) __irq { > EXTINT = 2; // Clear EINT1 interrupt flag > IENABLE; // allow nested interrupts > delay (); > ++intrp_count; // increment interrupt count > IDISABLE; // disable interrupt nesting > VICVectAddr = 0; // Acknowledge Interrupt > } > > So going back to the first code, exactly what happens in the > identify_and_clear_source() function? Does it clear both the interrupt > source module and the interrupt controller? Can this single function > really know about all interrupt sources and how you clear them (they > can be hundreds in a large soc)? Or do we have to implement one such > function for each interrupt (in which case, the name > identify_and_clear_source doesn't make sense).
The ARMv7 architecture already has a vectored interrupt controller supporting reentrant interrupts and interrupt priorities. What exactly are you trying to achieve ?
On 2012-02-22, Marek Omama <abc.junkaccount@gmail.com> wrote:
> > > I am trying to figure out how reentrant interrupts are supposed to > work on ARMv7. According to the ARM document DUI0203H, this is what > you have to do: >
First, is it really a ARMv7 class device, or is it a ARM7 (which is a ARMv4) class device ? I would expect all ARMv7 class devices to implement some form of priority nesting. Second, which MCU specifically is it ?
> Example 6.16. Nested Interrupt (ARMv6, non-vectored interrupts) > IRQ_Handler > SUB lr, lr, #4 > SRSFD #0x1f! ; Save LR_irq and SPSR_irq to System > mode stack > CPS #0x1f ; Switch to System mode > STMFD sp!, {r0-r3,r12} ; Store other AAPCS registers > AND r1, sp, #4 > SUB sp, sp, r1 > STMFD sp!, {r1, lr} > BL identify_and_clear_source ; *** 1 ***
You need to understand _why_ you need to do this at this point. What you do at this point needs to prevent the same interrupt from been triggered again when you enable IRQ interrupts in the CPSR below. What I do at this point is to use the MCU's priority handling mechanism instead. A couple of examples: On something like the SAM7S series, with automatic priority handling, that's little more than setting the interrupt priority during interrupt setup in the initialisation routines and then writing the EOI register at the end of this routine. I can't remember off the top of my head if I needed to do anything extra within this routine itself. On something like the LPC3131 series, this is the point at which I would set the priority limiter to the current interrupt priority so that only higher priority interrupts would be triggered when you update the I bit below. I save the old priority on the stack so that I can restore it at the end of this routine. In either case, I only access the peripheral specific registers in one area and that's in the C level interrupt handler, not in this interrupt wrapper.
> CPSIE i ; Enable IRQ > BL C_irq_handler ; *** 2 *** > CPSID i ; Disable IRQ > LDMFD sp!, {r1,lr} > ADD sp, sp, r1 > LDMFD sp!, {r0-r3, r12} ; Restore registers > RFEFD sp! ; Return using RFE from System mode > stack > > The problem I see is that the interrupt usually needs to be > acknowledged and cleared at two locations, once for the source of the > interrupt and once for the interrupt controller and in that order. The > snippet below (from ARM Knowledge Base on nested interrupts) > illustrates this on the first and last lines: >
It would help if we knew which MCU you are referring to; these details are _very_ MCU specific. (You may end up clearing the interrupt controller automatically by reading the peripheral's interrupt source register, for example.)
> > /* External Interrupt 1 Service */ > void eint1_srv (void) __irq { > EXTINT = 2; // Clear EINT1 interrupt flag > IENABLE; // allow nested interrupts > delay (); > ++intrp_count; // increment interrupt count > IDISABLE; // disable interrupt nesting > VICVectAddr = 0; // Acknowledge Interrupt > } > > So going back to the first code, exactly what happens in the > identify_and_clear_source() function? Does it clear both the interrupt > source module and the interrupt controller? Can this single function > really know about all interrupt sources and how you clear them (they > can be hundreds in a large soc)? Or do we have to implement one such > function for each interrupt (in which case, the name > identify_and_clear_source doesn't make sense). > > If anyone could explain this to me, or point me to some snippets, > articles or literatures explaining this I would be very grateful.
Simon. -- Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP Microsoft: Bringing you 1980s technology to a 21st century world
Thank you all for your answers,

I am targetting current/last generations of ARM so I am interested in
ARMv6-7, however most articles and tutorials I have found on the net
target ARMv4 and ARMv5 (ARM7-ARM9). I am simply trying to understand
how a bare metal code or a simple OS with preemptive multitasking may
work on ARM.

Any pointers to information explaining the differences between current
(v6-7) and the old way (v4-5) of doing this is most appreciated. Also,
I still don't understand how ARMv7 interrupt controllers by themselves
provide interrupt nesting and priority. Any explanation or examples on
this is also most welcome.

A more concrete question I have is how these handlers can guarantee
that you don't jump back into the same handler as soon as you enable
interrupts (an interrupt inside an interrupt, maybe both of the same
type)? I don't see anything protecting the saved context from
modification in the snippets above?

thank you in advance
On 02/25/2012 11:10 AM, Marek Omama wrote:
> > Thank you all for your answers, > > I am targetting current/last generations of ARM so I am interested in > ARMv6-7, however most articles and tutorials I have found on the net > target ARMv4 and ARMv5 (ARM7-ARM9). I am simply trying to understand > how a bare metal code or a simple OS with preemptive multitasking may > work on ARM. > > Any pointers to information explaining the differences between current > (v6-7) and the old way (v4-5) of doing this is most appreciated. Also, > I still don't understand how ARMv7 interrupt controllers by themselves > provide interrupt nesting and priority. Any explanation or examples on > this is also most welcome.
Here's a reference manual on the ARMv7M architecture, explaining in detaul how the exception handling works. http://unsynchronized.org/ninjabadge2010/datasheets/mcu/DDI0403D_arm_architecture_v7m_reference_manual.pdf
> A more concrete question I have is how these handlers can guarantee > that you don't jump back into the same handler as soon as you enable > interrupts (an interrupt inside an interrupt, maybe both of the same > type)? I don't see anything protecting the saved context from > modification in the snippets above?
Given the fact that modern ARMv7 devices usually have dozens of different interrupt vectors, and an 8-bit priority level, I assume that there's very little need for re-enabling interrupts inside the handler for the exact same interrupt source. Do you foresee any concrete need for that in your projects ? The ARMv7 automatically saves your context on a stack, and then calls the correct vectored handler. When a higher priority interrupt comes in, it does the same thing. It just pushes the new context on the stack. When the 2nd interrupt is finished, the old context is popped from the stack, and the CPU continues with the first interrupt handler.
Marek Omama <abc.junkaccount@gmail.com> wrote:

> Any pointers to information explaining the differences between current > (v6-7) and the old way (v4-5) of doing this is most appreciated. Also, > I still don't understand how ARMv7 interrupt controllers by themselves > provide interrupt nesting and priority. Any explanation or examples on > this is also most welcome.
The CPU automatically saves the context on the stack, which both enables nested interrupts and lets you write the ISR completely in C. Each ARMv7-M core implements between 3 and 8 bits of interrupt priority. The priority is divided into preemption priority and subpriority. An interrupt with a lower preemption priority can preempt an interrupt with a higher one, and an interrupt with a lower subpriority will be handled before one with the same preemption priority but higher subpriority. If both preemption priority and subpriority are the same, the interrupt with the lowest vector number is handled first. Apart from a few system interrupts, you are free to assign priorities to interrupts as you see fit.
> A more concrete question I have is how these handlers can guarantee > that you don't jump back into the same handler as soon as you enable > interrupts (an interrupt inside an interrupt, maybe both of the same > type)? I don't see anything protecting the saved context from > modification in the snippets above?
This is handled by the hardware. When the CPU detects an interrupt, it is flagged as pending. When the interrupt handler starts executing, the interrupt is flagged as active, and the pending bit is cleared. The active bit is cleared when the ISR exits. If the interrupt is triggered while the ISR is active, it will be marked as pending, but the CPU will not trigger an already active ISR. Instead, the ISR will be re-triggered immediately on exit. Joseph Yiu's "The Definitive Guide to the ARM Cortex-M3" has pretty good coverage of the ARMv7-M NVIC if you find the ARM architecture manuals hard reading. -a