This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Reentrant interrupt handler (Need working source code)

Other Parts Discussed in Thread: SYSBIOS, RM48L952, HALCOGEN

Where could I find reentrant interrupt routine sample code that work with IRQ and/or FIQ. I am sure that working source code will speak by itself and will help other developper also.

 

I found some example from arm website documentation that switch to System mode but it faill after a shotrt time at exeution.  I test it using a FIQ NHET interruption at 32Khz. . I seem that sometimes there it perform return att SRSR register instead of LR address.

 

 

http://infocenter.arm.com/help/topic/com.arm.doc.dui0471e/DUI0471E_developing_for_arm_processors.pdf

Reentrant interrupt handlers

If an interrupt handler enables interrupts before calling a subroutine and another interrupt occurs, the return address of the subroutine stored in the IRQ mode LR is corrupted when the second IRQ is taken. This is because the processor automatically saves the return address into the IRQ mode LR for the new interrupt overwriting the return address for the subroutine. This results in an infinite loop when the subroutine in the original interrupt tries to return.

A reentrant interrupt handler must save the IRQ state, switch processor modes, and save the state for the new processor mode before branching to a nested subroutine or C function. It must also ensure that the stack is eight-byte aligned for the new processor mode before calling AAPCS-compliant compiled C code that might use LDRD or STRD instructions or eight-byte aligned stack-allocated data.

Using the __irq keyword in C does not cause the SPSR to be saved and restored, as required by reentrant interrupt handlers, so you must write your top level interrupt handler in assembly language.

In ARMv4 or later you can switch to System mode if you require privileged access.

Note

This method works for both IRQ and FIQ interrupts. However, because FIQ interrupts are meant to be handled as quickly as possible there is normally only one interrupt source, so it might not be necessary to provide for reentrancy.

The steps required to enable interrupts safely in an IRQ handler are:

  1. Construct the return address and save it on the IRQ stack.

  2. Save the work registers, non callee-saved registers and IRQ mode SPSR.

  3. Clear the source of the interrupt.

  4. Switch to System mode, keeping IRQs disabled.

  5. Check that the stack is eight-byte aligned and adjust if necessary.

  6. Save the User mode LR and the adjustment, 0 or 4 for Architectures v4 or v5TE, used on the User mode SP.

  7. Enable interrupts and call the C interrupt handler function.

  8. When the C interrupt handler returns, disable interrupts.

  9. Restore the User mode LR and the stack adjustment value.

  10. Readjust the stack if necessary.

  11. Switch to IRQ mode.

  12. Restore other registers and IRQ mode SPSR.

  13. Return from the IRQ.

The following examples show how this works for System mode.

Example 37. Reentrant interrupt handler for ARMv4/v5TE

    PRESERVE8
    AREA INTERRUPT, CODE, READONLY
    IMPORT C_irq_handler
    IMPORT identify_and_clear_source

IRQ_Handler
    SUB     lr, lr, #4             ; construct the return address
    PUSH    {lr}                   ; and push the adjusted lr_IRQ
    MRS     lr, SPSR               ; copy spsr_IRQ to lr
    PUSH    {R0-R4,R12,lr}         ; save AAPCS regs and spsr_IRQ
    BL      identify_and_clear_source
    MSR     CPSR_c, #0x9F          ; switch to SYS mode, IRQ is
                                   ; still disabled. USR mode
                                   ; registers are now current.
    AND     R1, sp, #4             ; test alignment of the stack
    SUB     sp, sp, R1             ; remove any misalignment (0 or 4)
    PUSH    {R1,lr}                ; store the adjustment and lr_USR
    MSR     CPSR_c, #0x1F          ; enable IRQ
    BL      C_irq_handler
    MSR     CPSR_c, #0x9F          ; disable IRQ, remain in SYS mode
    POP     {R1,lr}                ; restore stack adjustment and lr_USR
    ADD     sp, sp, R1             ; add the stack adjustment (0 or 4)
    MSR     CPSR_c, #0x92          ; switch to IRQ mode and keep IRQ
                                   ; disabled. FIQ is still enabled.
    POP     {R0-R4,R12,lr}         ; restore registers and
    MSR     SPSR_cxsf, lr          ; spsr_IRQ
    LDM     sp!, {pc}^             ; return from IRQ.
    END


Example 38. Reentrant Interrupt for ARMv6 (non vectored interrupts)

    PRESERVE8
    AREA INTERRUPT, CODE, READONLY
    IMPORT C_irq_handler
    IMPORT identify_and_clear_source

IRQ_Handler
    SUB         lr, lr, #4
    SRSDB       sp!,#31          ; Save LR_irq and SPSR_irq to System mode stack
    CPS         #031             ; Switch to System mode
    PUSH        {R0-R3,R12}      ; Store other AAPCS registers
    AND         R1, sp, #4
    SUB         sp, sp, R1
    PUSH        {R1, lr}
    BL          identify_and_clear_source
    CPSIE       i                ; Enable IRQ
    BL          C_irq_handler
    CPSID        i               ; Disable IRQ
    POP         {R1,lr}
    ADD         sp, sp, R1
    POP         {R0-R3, R12}     ; Restore registers
    RFEIA       sp!              ; Return using RFE from System mode stack
    END

  • Sylvestre,

    The key element in re-entrant IRQ is as explained in ARM documentation to store LR and used register in the stack (IRQ stack)

    If the code you tried is working for sometimes and than fails, I will highly suspect a stack overflow problem.
    So I will suggest that you look at your IRQ_SP and see if you have allocated enough space.

    Best Regards,

    Jean-Marc

  • Hello Jean-Marc

    I finally make the FIQ interrupt handle re-entrant.  I send you the working piece of code in assembler with a pseudo code in C. but I still have a question.

    It is working using SVC stack 0x13 to perform C handler code. 
    I tried it using System stack mode 0x1F and it doesn't work. 
    The stack size on the systemStack is 8k and the stack size of the SVC is 1k

    The system stact is also used by the sysbios RTOS and when the interrupt return to application stack it sometimes hang on 0x1c exeption.  When I look at the LR register of the given exeption it appear that the interrupt returned at the SPSR address instead of the LR address.  I suspect a ARM to THUMB misalignement at the retrun from interrupt.  Is it possible?

     //============== HandleFiqC.c ====================== 
    void HandleFiqC()
    {
       // Detect interrupt source
       // Disable interrupt source
       // enable_fiq();
       // calI interrupt routine
       // return to interrupt handler in assembler
    }
    //=============================================== 

    ;============== FiqInterruptHandler.asm ====================== 
    .state32
    .asg HandleFiqC, _HandleFiqC              
    .asg HandleFiqAsm, _HandleFiqAsm

    .global _HandleFiqC        ; Native C interrupt entry point
    .global_HandleFiqAsm    ; Assembler FIQ interrupt handler

    ; Solution for a re-entrant interrupt handler
    ; Re-entrant interrupt handlers, however, are more complex.
    ;
    ; ----------------- AAPCS --------------------
    ; http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf
    ; •r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
    ; •r4-r8 are callee-save registers
    ; •r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
    ; •r10-r11 are callee-save registers
    ; •r12-r15 are special registers
    ;
    ; ----------------- ABI 8 byte alignement --------------------
    ; http://infocenter.arm.com/help/topic/com.arm.doc.ihi0046b/IHI0046B_ABI_Advisory_1.pdf 8 byte alignement
    ;
    ; ----------------- Nesting interrupt schema and explaination --------------------
    ; http://www.iti.uni-stuttgart.de/~radetzki/Seminar06/08_report.pdf
    ;
    ; ----------------- ARM doc --------------------;
    ; http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka13552.html
    ; ---- Interrupt entry flowchart and NMI
    ; http://infocenter.arm.com/help/topic/com.arm.doc.ddi0363g/DDI0363G_cortex_r4_r1p4_trm.pdf
    ; ---- sample code
    ; http://infocenter.arm.com/help/topic/com.arm.doc.dui0471e/DUI0471E_developing_for_arm_processors.pdf

    .align8
    .text
    .arm
    _HandleFiqAsm:
    SUB lr, lr, #4                      ; Adjust LR to point to return
    SRSFD #0x11 !                 ; Use SRS to save LR_fiq and SPSP_fiq on the FIQ stack
    CPS #0x13                       ; Switch to reentrant Selected mode (SVC:0x13 SYSTEM:0x1F)
    PUSH {R0-R3, R9, R12} ; Store AAPCS regs on to Selected stack save R9 because the use are determined by the execution environment
                                                  
     ; vstmdb {D0-D7}, r13! ; save vfp scratch regs on NEON environnement
       MOV r1, sp
       AND r1, r1, #4               ; Ensure 8-byte stack alignment…
       SUB sp, sp, r1              ; …adjust stack as necessary
       PUSH {r1, lr}                 ; Store adjustment and previous LR LR_svc (push 2 register will keep 8 byte stack alignement)
       BL _HandleFiqC            ; Branch to 2nd level handler
                                                     
     ; C handler is responsible to clear IRQ source
                                                      
    ; then C handler may reenable FIQ
                                                      
                                                      
    ; Disable FIQ/IRQ
                                                       
    ; CPSID f ; use to Disable FIQ (On TMS570, FIQ are used as NMI. Once the F bit is cleared in the CPSR, it can't be set anymore)
                                                      
    ; CPSID i ; use to Disable IRQ
                                                      
    ; on tms570 we cant disable fiq by software
        POP {r1, lr}                 ; Restore stack alignement offset an previous LR of the Selected mode
        
    ADD sp, sp, r1            ; Un-adjust stack
                                                     
    ; vldmia {D0-D7}, r13! ; save vfp scratch regs on NEON environnement

        
    POP {R0-R3, R9, R12} ; Restore AAPCS registers

    CPS #0x11                     ; Switch to FIQ mode
    RFEFD sp!                      ; Return from the FIQ mode using stack

    ;===================================================== 

    Regards,

    David Sylvestre

  • David,

    your code for the re-entrant interrupt handler looks good, there might be a detail with the VIM which you also should consider.
    You should modify the VIM mask to only allow higher priority interrupts than the current one, so that it cannot interrupt itself or be interrupted by a lower priority interrupt.

    I attached a CCS project, which shows how nested IRQ interrupts can be implemented on a RM48 MCU. Project can now be found here: http://e2e.ti.com/support/microcontrollers/hercules/f/312/p/164944/905772.aspx#905772

    Best Regards,
    Christian

  • David,

    What is the status on your post? Did I answered your question?

    If so, can you mark it as "Verified Answer" so I can close this thread?

  • Hello David,

    For my actual project I will need the nested interrupts too. Thank you for your CCS project, this helps a lot!

    I still have one question left: Is it necessary to do save and restore of REQENASET0 in the asm? Would this not be possible in the C source? Or do I miss something?

    The reason is: I will need to add some code to support all IRQ, and I'm much more comfortable in C than in asm.

    Thank you for an answer!

    Regards,

    Roger

  • Hi Roger,

    To my knowledge, there is no way to intentionally push and pup values to to and from the stack in pure C.
    Therefore I did this in the assembly.
    You could also implement a LiFo in C, but this would cause much more code overhead and thus decreases the system performance overall.

    Best Regards,
    Christian

  • Hello Christian,

    Thank you for your quick reply! Yes, push/pop are not available in C, but with this you do store/restore the REQMASKSET0 register at 0xfffffe30, because it will be manipulated in the C code.

    Assuming the code below in C (C_irqDispatcher). Would this code not do the same as the push/pop in asm? Thank you for your help!

    function

    {

      // store REQMASKSET0 to local var

      uint32_t origReqMaskSet0 = vimREG->REQMASKSET0; // this is 0xfffffe30

      // now modify interrupts with REQMASKCLR0 to my needs, enable_irq, call isr, disable_irq

      // restore REQMASKSET0 from local var

      vimREG->REQMASKSET0 = origReqMaskSet0;

    }

  • I guess you are right, I thought way to complicated.

    Please note, that you might also need to store REQMASKSET1 and REQMASKSET2, if you use them.

    BR,
    Christian

  • Thanks & have a nice day!

    I'm going to do some tests now...

  • Hi Christian,

    First I would like to say to you thank you for the project and I have some question.

    1. I notice that in irqDispatch_c u are calling _enable_IRQ(); function which I can't find inside of project.

    I made my on function which look like this:

    _enable_IRQ_interrupt_

    cpsie i

    .endasmfunc

    but i think that your function call works better. Why? 

    2. I have some IRQ interrupts which are set each 1ms and some FIQ which are for RS232 and NHET. So for FIQ do i have to also clr mask in irq_Dispatch.

    Which things do i have to be the most beware of?


     

  • As tip: Christian did publish an updated code here: http://e2e.ti.com/support/microcontrollers/hercules/f/766/t/210653.aspx?pi239031349=3

    I'm using this code (for RM48L952) and it works well! Thank you Christian too!

    I'm using the asm code as published and made my own nesting rules in irqDispatcher_c.c (not priority defines the nesting, but some application definitions)

    What I did have to add is to init the LR register of System Mode (was not done in _coreInitRegisters_ (HALCoGen code). I addded this in a user code section just after the call  of _coreInitRegisters_() (in sys_startup.c).

        asm(" mov r0, lr"); // store lr to r0

        asm(" cps #31"); // switch to system mode
        asm(" mov lr,r0"); // init system mode LR
        asm(" cps #19"); // switch back to supervisor mode

    And adjust Stack size (because system mode stack is now used, but IRQ stack is not used anymore)

    And enable the irqDispatch mode, and disable the VIC mode.

    In my application I do only use 1 FIQ which is most important (FIQ does interrupt IRQ). All other interrupts are set to be IRQ, so I can nest them if needed. Not sure how to manage for nesting FIQ . Do you really need FIQ for RS232?

    Regards,

    Roger

  • Hi Roger,

    Thanks for a quick reply.  I will check new link which you gave me. Im using also RM48L952.

    I dont think that i need  RS232 to be FIQ, but for now this is only solution that my application is running.

    I have also one strange behaviour of the device, that is on debug session device works and after a while sometimes i get a prefect error.
    After restart my device is working until i enable IRQ. Did you have the same problems?

    IRQ interrupts for now: RTI 0,1,2,3

    My stack size is 0x4000, code:

    userSp .word 0x08000000+0x00000800
    svcSp .word 0x08000000+0x00000800+0x00001A00
    fiqSp .word 0x08000000+0x00000800+0x00001A00+0x00000E00
    irqSp .word 0x08000000+0x00000800+0x00001A00+0x00000E00+0x00000E00
    abortSp .word 0x08000000+0x00000800+0x00001A00+0x00000E00+0x00000E00+0x00000100
    undefSp .word 0x08000000+0x00000800+0x00001A00+0x00000E00+0x00000E00+0x00000100+0x00000100


     

  • Roger,

    thanks for posting your hints on this topic.

    Dejan,

    In general FIQ should be reserved for high critical interrupts, which is the ESM interrupt in many applications.

    The  _enable_IRQ() isn't a normal function it is an intrinsic, which is directly translated to asm:

    MRS dst , CPSR
    BIC tmp , dst , #0x80
    MSR CPSR , tmp

    This is described in the compiler manual: SPNU151H Page 139 (http://www.ti.com/lit/ug/spnu151h/spnu151h.pdf#page=139)

    The prefetch aborts you are seeing might be because to missing ECC for your code or parts of it. Try to specify a fill value for the linker and make sure, that you generate and program the ECC code correctly.

    Best Regards,
    Christian

  • Thanks.

    For now i have disabled ECC and ESM. I will implement functions and give you a feedback.  

    Roger and Christian, i can't find a new project which is not from May. Can you post it here?

    Thanks.

  • You are right, I do not find it anymore...

    Did you remove it, Christian?

    This is what I downloaded on 25th february (3 weeks ago):

    2630.Nested_Interrupts20120913.zip

  • Christian, the project 4786 has less code (2 RTI compares, irqDispatch not have limits for nesting, sys_startup is smaller) of the project 2630 which Rogers just post.

    I can report that by adding esmInit(); into sys_startup and adding irqDispatch nesting limit now works better, but a i have still some SW problems.
    When i will resolve them i will get back to you. 

    Again thanks to both of you.

  • Im still having problems with nesting and i dont know what is the problem.

    Your project works and my not. Some models were not the same such as rti and settings of pll,...
    So i change all models to be the same (main(), _c_int00, sys_core.c, system.c). And problems remains.

    Now the only change is that my files are linked, and i have much more .text (604.000) than your project (12.000).

    Debuging problem:
    I get rti 0 interrupt. I go to irqDispacher, executes  (*irq_func_pnt)(); ->  goes to rti 0 ISR goes does all operation

    returns form rti ISR and  in asm executes BX after the call (*irq_func_pnt)(); (_irqDisable() never accrues) and than again goes to rti 0 ISR and creates endless loop ((*irq_func_pnt)(); - > ISR RTI0 compare ->  Nodification ->  ISR RTI0 compare -> (*irq_func_pnt)(); -> ... ).
    In register of RTI-FLG is 0xE which means that flag is cleared for RTI 0.

    After this only FIQ are working, all other normal opreations are stoped because program is in nonstop in irqDispacher. 

    Now im changing your project to get the same behaviour.    
    I created some dynamic and static variables into your project, i had to add .sysmem | RAM into sys_link.cmd but for now your project still works.
    Now im will try to add some more .text into your project.

    Do you have some other idea why i have this kind of behaviour? 

  • Do you have specified in HALCoGen that you use dispatcher mode for IRQ?

    Has the function rtiCompare0Interrupt an #pragma interrupt in front of it?

    Best Regards,
    Christian

  • Thank you for fast reply. Can you be more specific where in code I will find if I use dispatcher mode?

    sys_intvecs.asm looks like so:

    ;-------------------------------------------------------------------------------
    ; sys_intvecs.asm
    ;
    ; (c) Texas Instruments 2009-2010, All rights reserved.
    ;

    .sect ".intvecs"


    ;-------------------------------------------------------------------------------
    ; import reference for interrupt routines

    .ref _c_int00
    .ref _undef
    .ref _svc
    .ref _prefetch
    .ref _data
    .ref _irqDispatch


    ;-------------------------------------------------------------------------------
    ; interrupt vectors

    b _c_int00
    b _undef
    b _svc
    b _prefetch
    b _data
    reservedEntry
    b reservedEntry

    ;Call Int via dispatcher
    b _irqDispatch

    ;Call FIQ direct
    ldr pc,[pc,#-0x1b0]

    -----------------------------------

    Yes i have pragma.

    My function look like so:

    #pragma CODE_STATE(rtiCompare0Interrupt, 32)

    #pragma INTERRUPT(rtiCompare0Interrupt, IRQ)
    //some comment

    void rtiCompare0Interrupt(void)
    {

    rtiREG1->INTFLAG = 1U;
    rtiNotification(rtiNOTIFICATION_COMPARE0);

    }

     -------------------------------------------------------------

    So i removed pragma and now it works :D THANK YOU! 

  • If you use HALCoGen than you can specifiy the dispatcher mode here:

    If you do so, I would expect the "#pragma interrupt" to disappear  infront of each IRQ function.

    The "#pragma interrupt" will cause the compiler to insert a special return sequence for returning from an interrupt. But if you use a software dispatcher, as you do if you use reentrant interrupts, than you want a normal return sequence, as for every normal function.

    Good to hear that it now works for you.

    Best Regards,
    Christian

  • Hi Christian,

    Please take a look at this thread if you like to:

    http://e2e.ti.com/support/microcontrollers/hercules/f/312/p/255967/902820.aspx#902820

    In there, Sunil says that a read of VIM register could lead to problems if reading more than one.

    So these 2 lines in the code published could lead to problems:

    uint32_t vec = vimREG->IRQIVEC;
    void (*irq_func_pnt)(void) = (void (*)(void))vimREG->IRQVECREG;

    I did encounter problems which were: vec == ok and irq_func_pnt was phantomInterrupt.

    So long,

    Roger

  • Hi Roger,

    thanks for the hint, I will see how to adjust the example code to handle this major change.
    For now I think it would be impossible to use the IRQVECREG when doing nested interrupts and that it is necessary to do a interrupt vector address look-up in SW.

    Best Regards,
    Christian

  • All,

    Here is a new CCS Project for the RM48 which shows how interrupt nesting could be implemented.

    Now I load the ISR vector address directly from the VIM RAM address table and not from the IRQVECREG register. This could potentially be optimized by loading the ISR vector address from the embedded SRAM instead, which might be slightly faster compared to loading it from the VIM RAM.

    Best Regards,
    Christian