ISR stack

m_a_d_i_s wrote on Wednesday, October 24, 2007:


I have an interesting problem here related to ISR-s. ISR-s are started as in case of FreeRTOS demos etc.:

void vUSB_ISR( void ) __attribute__ ((naked));
void vUSB_ISR( void )
    /* This ISR can cause a context switch.  Therefore a call to the
    portENTER_SWITCHING_ISR() macro is made.  This must come BEFORE any
    stack variable declarations. */

    /* Now variables can be declared. */

Which is followed by local variable declarations. But I noticed that when I declare too many local variables, funny things will happen. So I started to debug the entering to ISR-s. IRQ stack pointer is located at 0xfff0 as expected (part is SAM7X256 with 64K RAM and I debug in RAM). Stack sizes are declared like:
    /* Stack Sizes */
    .set  UND_STACK_SIZE, 0x00000004
    .set  ABT_STACK_SIZE, 0x00000004
    .set  FIQ_STACK_SIZE, 0x00000004
    .set  IRQ_STACK_SIZE, 0X00002000
    .set  SVC_STACK_SIZE, 0x00000800
So IRQ stack is empty. But do to the naked attribute stack pointer is not modified while entering to the ISR. I think thats the expected action, as there is asm macro included, that enter_isr? But now the local variables are situated above the stack pointer…? So in case I declare enough local variables in ISR, it overwrites my vector table. I verified it with debugger. while declaring the local variables and initialising them, asm code generated is:

    portCHAR cTaskWokenByPost = pdFALSE;
0x00001454 <vAudio_ISR+96>:  mov   r3, #0    ; 0x0
0x00001458 <vAudio_ISR+100>: strb  r3, [sp, #11]

while stack pointer stays at the beginning of IRQ stack 0xfff0.

C flags are:
CPFLAGS = $(MCFLAGS) -mthumb-interwork $(OPT) -ggdb -fomit-frame-pointer -ffunction-sections -fdata-sections -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wno-strict-aliasing -fverbose-asm -Wa,-ahlms=$(<:.c=.lst) $(DEFS)
CPFLAGS += -MD -MP -MF .dep/$(@F).d

arm-elf-gcc version is 4.1.1 which was included with yagarto toolchain.

Can anybody point me whats wrong with my code, cflags or something else?

Thank you,

davedoors wrote on Wednesday, October 24, 2007:

Did you change the portENTER_SWITCHING_ISR() macro at all?  Looking at the comments in the macro it seems to have this point covered by switching stacks:

/* Save the context of the interrupted task. */   
/* We don’t know the stack requirements for the ISR, so the frame */
/* pointer will be set to the top of the task stack, and the stack*/
/* pointer left where it is.  The IRQ stack will get used for any */
/* functions calls made by this ISR. */               
asm volatile ( “SUB        R11, LR, #4” );           

I think the last bracket is also very important.

m_a_d_i_s wrote on Wednesday, October 24, 2007:

No, I did not change the portENTER_SWITCHING_ISR() macro… Should I? Maybe I have missed an important point here.

Thank you,

m_a_d_i_s wrote on Thursday, October 25, 2007:

But what about the demo projects provided with the FreeRTOS download? They use the same portENTER_SWITCHING_ISR() but just dont declare too many local variables in serial ISR. But I think these local variables are also situated above the IRQ stack? Any comments/guidance?


davedoors wrote on Thursday, October 25, 2007:

I don’t fully understand your problem, and find it odd that you would want to allocate so many variables within the ISR that you take up 0x2000 bytes of stack.  However, it seems from the assembly code you provide that you could run into problems when making function calls from within the assembly routine as the function could use the same stack space.

I note you have the -fomit-frame-pointer compiler option set.  This option was added to get around a bug in the GCC code generation.  It might be though that this is what is causing your problem.  I think this because of the comment in the macro that states the frame pointer is being moved to the task stack, but you are not using a frame pointer.  I would have to look at the entire assembly code output to know if this actually was a problem or not.

sotd wrote on Thursday, October 25, 2007:

but the stack grows down, so its writing off the bottom.

rtel wrote on Thursday, October 25, 2007:

Looks like you might have a point here Dave ref the -fomit-stack-pointer option.  I will check this out this evening and report back.  In the mean time you can make your variables static (don’t forget to manually initialise them then), or turn optimisation up to 1.  This should get you going in the short term.


anonymous wrote on Thursday, October 25, 2007:

Richard, et al

As you may recall, I have never accepted -fomit-frame-pointer as an acceptable solution to the FreeRTOS/GCC ARM context switch issue. The -fomit-frame-pointer impedes stack tracebacks too much for me.

I hope that -fomit-frame-pointer is not the problem in in this case, but if it is, I have previously posted a fix to the portSAVE_CONTEXT() macro. Well, as it turns out that fix didn’t quite do it under certain optimization levels (note that I use -fno-omit-frame-pointer when I optimize, so I still have the problem under optimization). The 8 bytes of open space I left on the stack needs to be increased to 32 bytes. The number has been arrived at purely by experimentation. 16 is too small, 32 seems to work.

The good news is that a colleague of mine recently did some further experimentation and believes that GCC 4.2.1 finally fixes the underlying problem in the ARM/Thumb code generation. We’ll see.

Note that the fix to portSAVE_CONTEXT() is reasonably inocuous even if the GCC thing is fixed.


rtel wrote on Thursday, October 25, 2007:

Hi Glen, et al :o)

I’m just looking at this again now and coming to the conclusion that probably the safest already solution is to declare all local variables in the interrupt service routine as static.  This way it does not matter whether the frame pointer is used or not.  This would also mean that the line “asm volatile ( “SUB        R11, LR, #4” );” could be removed from the portENTER_SWITCHING_ISR() macro [it does not do anything when the frame pointer is not used anyway].  This should then work will all compiler options at all optimisation levels. 

What do you think?

Also, probably the reason the amount of RAM you have to leave spare on entry to interrupts (with your previous work around) varies is simply because the stack requirement of the ISRs varies?


rtel wrote on Thursday, October 25, 2007:

I have added the following to the list of known issues: 

ARM7 GCC demos
Some time back the compiler option -fomit-frame-pointer was introduced to all the ARM7 GCC demos as a work around for a GCC bug.  It has recently been brought to my attention that since then interrupts that can cause a context switch have the potential to cause memory corruption as the macros used assume the existence of a frame pointer.  As a workaround, all variables declared locally within an ISR should be declared static - when doing this don’t forget to initialise the variables manually within the C code, not just where the variable is declared.


anonymous wrote on Friday, October 26, 2007:


I don’t fully comprehend the specific ISR issue that triggered this discussion, so I can’t really comment on that. Certainly I avoid using auto variables in functions with portENTER_SWITCHING_ISR() but I can’t recall the details as to why it was necessary.
It made sense at the time - I don’t recall if I read the advice or got it direct from you after explaining a problem.

On the issue of my workaround, no, it has nothing to do with ISR stack requirements. It’s all about how GCC fiddles the stack pointer on returns from functions.

The point is this… under certain circumstances, details and rationale elude me, GCC generates code to return from functions in a bad way. Sometimes the stack pointer gets (briefly) pulled back behind valid stack data (such as the return address), then it is adjusted forward again, and the return address, etc. are popped off. This apparently only happens when no-omit-frame-pointers is in effect. The max. amount it gets pulled back beyond valid data varies, but my current guess is that it is less than 8 long words (32 bytes).

Now if an interrupt happens and FreeRTOS saves a task context just when the sp is badly positioned, boom, the valid data gets clobbered.

One solution, and that used by some other kernels, is to save the context in the TCB rather than on the system/user stack. My solution was stick with your design (and ARM port) but modify it to skip 32 bytes (originally 8) before pushing the context. That skips over the potentially valid data so that it doesn’t get clobbered. Your context restore is robust against this change.

A colleague has pointed out to me that the GCC issue is a serious problem for those who want nested interrupts, even if there is no multitasking kernel. It’s almost a requirement that things get pushed on the operational stack. This he argues, demonstates that it truly is a bug in GCC code generation (not merely an idiosyncracy). We are both relieved that it finally appears to be fixed in GCC 4.2.1.

m_a_d_i_s wrote on Friday, October 26, 2007:

Dave wrote:
>I don’t fully understand your problem, and find it odd that you would want to allocate so many variables >within the ISR that you take up 0x2000 bytes of stack. However, it seems from the assembly code you provide >that you could run into problems when making function calls from within the assembly routine as the function >could use the same stack space.

I dont use 0x2000 bytes of stack in IRQ (I just reserved more stack as "funny things happened" within ISR). The assembly code I provided is generated by gcc. And shows how it initialises local variables, which are not static. These local variables get saved above the IRQ stack pointer, which is set to 0xfff0 (running from RAM and part is SAM7X256 so its at the end of RAM) by boot.S. I.e. no nested interrupts, just first interrupt arrives and stack is empty. And the situation is the same for all the interrupt functions, Im afraid. Local variables just happened to fit upon IRQ stack without overwriting vector table. Static variables dont cause any problem.

I changed ISR variables to static in my current project. But another solution would be to move IRQ stack down. Like declare it in two parts in boot.s. First make room for "unspecified features" and then save IRQ sp.


rtel wrote on Sunday, October 28, 2007:

See the following thread: