ARM problem with ISR local automatic variable

nobody wrote on Saturday, November 26, 2005:

My ARM port is based on the ARM7_LPC2000 demo. I am using portENTER_SWITCHING_ISR() as is, in which R11 (FP) is set to LR as the last step. This leaves the FP as the SP of the interrupted task. This is nice, except that the compiler seems to be generating code for my  __attribute__ ((naked)) ISR such that the offsets to autos from the FP are positive rather than negative (as is the case with normal functions). This then clobbers stuff on the task’s stack.

Is this a known issue? Or have others seen it?

My fix has been to use statics. Functions called from the ISR are OK, of course.

nobody wrote on Saturday, November 26, 2005:

Do you declare your local variables AFTER the call to portENTER_SWITCHING_ISR()?  If you don’t do this then you will get the problem you describe. 

From the demo serial driver:

void vUART_ISR( void )
{
    /* This ISR can cause a context switch, so the first statement must be a
    call to the portENTER_SWITCHING_ISR() macro.  This must be BEFORE any
    variable declarations. */
    portENTER_SWITCHING_ISR();

    /* Now we can declare the local variables. */
    signed portCHAR cChar;
    portBASE_TYPE xTaskWokenByTx = pdFALSE, xTaskWokenByRx = pdFALSE;

    /* What caused the interrupt? */
    switch( UART0_IIR & serINTERRUPT_SOURCE_MASK )

etc…rest of function…

nobody wrote on Saturday, November 26, 2005:

No. Ise now that this problem was an illusion. I had two short ints as local variables. The first appears at the R11 address. The second appear at R11 + #2 since it is in the 4 bytes word located by R11.

Unfortunately this doesn’t resolve the underlying problem I am having which is that if I declare the ISR variables auto, my ulCriticalNesting variable ends up getting clobbered, but if I make them static everthing is fine. Maybe I am having a stack overflow issue.

nobody wrote on Saturday, November 26, 2005:

Yes, I agree, could be a stack overflow.

anonymous wrote on Saturday, November 26, 2005:

I am the originator of this thread. Just got a login id.

So my original hunch that the ISR auto variable were clobbering the interrupted task’s saved ulCriticalNesting value was correct. But as noted, the reason was not positive offsets from R11 (the Frame Pointer).

What I did discover is that the the R11 is set up incorrectly in portENTER_SWITCHING_ISR(). The final line
   asm volatile ( “MOV R11, LR” );
leaves R11 locating the saved ulCriticalNesting rather than a new (psuedo)frame on the interrupted task’s stack. I corrected it with
   asm volatile ( “SUB R11, LR, #4” );

I feel I may still be missing something because it seems odd that such a problem has not yet been found. Or maybe it has, the original subject line was somewhat misleading.

rtel wrote on Saturday, November 26, 2005:

Hi Glen,

I have a couple of questions about your setup:

1) Which GCC version are you using?
2) Are you using the GNUARM build (just for comparison with my own setup).
3) Have you modified the code within the Source/Portable/GCC/arm7_LPC2000 directory at all?

Would it be possible for you to forward me your project?  If so can you:

1) Create the smallest project possible that demonstrates the problem.
2) Build the project with debug info included and preferably no optimisation.
3) Zip up your entire build directory including the object code, elf file, FreeRTOS code and your application code and send the zip to the mail address on the FreeRTOS contacts page (r +DOT+ barry _at_ freerto…org).

I will hopefully then be able to simulate your build

Thanks,
Richard.

rtel wrote on Saturday, November 26, 2005:

Sorry, a couple of other questions:

THUMB or ARM build? … and …
What is the optimisation level?

rtel wrote on Sunday, November 27, 2005:

I have been playing around with this a bit and cannot replicate the reported behavior so far.  For example, with an ISR function of the form:

void ISR1( void )
{
____portENTER_SWITCHING_ISR();

____volatile short short1, short2;

____short1 = 0x1234;
____short2 = 0x5678;

____/* Just some dumy stuf to generate code. */
____if( short1 == short2 )
____{
________short1 = 0;
____}
   
____portEXIT_SWITCHING_ISR( 0 );   
}

I find that the local variables are located on the IRQ stack and the frame pointer is not used.  If I change this to:

short DummyFunction( short s1, short s2, long x, long y )
{
short a = 1;
short b = 2;
volatile long f = x + y;

____a += f;

____if( s1 > s2 )
________return s1 + a;
____else
________return s2 + b;
}

void vUART_ISR( void )
{
____portENTER_SWITCHING_ISR();

____volatile short short1, short2;
____long x = 0x12345678, y = 0x23456789;

____short1 = 0x1234;
____short2 = 0x5678;

____short1 = DummyFunction( short1, short2, x, y );
   
____portEXIT_SWITCHING_ISR( 0 );   
}

Then the frame pointer is used for local variables, but it is used correctly and the address pointed to by R11 is not overwritten.

I will wait to here back from you with the info requested before looking at this any further.

Regards.

rtel wrote on Saturday, December 03, 2005:

There does indeed appear to be a problem when using GCC V4.0.1 as follows:

Consider the following function fragment of a naked ISR function.

void vUART_ISR( void )
{
____portENTER_SWITCHING_ISR();
____short  needSwitch, level;
____needSwitch = false;

portENTER_SWITCHING_ISR() saves the context, and sets the frame pointer to point to the last context item to be saved.

The line needSwitch = false is then translated into the following code:

mov r3, #0
strh r3, [r11, #-14]

from which it can be seen that the needSwitch variable has an offset of 14 from the frame pointer.  However, the same code compiled with GCC V4.0.1 generates the following code

mov r3, #0
strh r3 [r11]

and in so doing writes the variable directly onto the memory pointed to by the frame pointer - clobbering the last stacked item.

It seems the FreeRTOS code is taking advantage of inefficiencies within the V3.4.3 version of GCC, that are no longer present in V4.0.1.

As the OP suggests, this can be fixed by a small modification to the portENTER_SWITCHING_ISR() macro.  Within portENTER_SWITCH_ISR() the line:

asm volatile ( "MOV R11, LR" );

should be changed to

asm volatile ( "SUB R11, LR, #4" );

Many thanks to the OP for pointing this out.  A fix will be released following further investigations.

Regards.