Hi,
While developing a program for a Coldifre 5212 microcontroller I noticed strange behavior of the prog. After some investigation I found that interrupt handling functions do not store all registers which are used inside the interrupt. They store registers used in the functions themselves but ignore all registers used in functions called inside the interrupts. This corrupts code execution.
In the forum I have found no working solution.
1) pragmas NO_ENTRY, NO_EXIT etc. do not work in CW at all.
2) I wrote an interrupt handler in assembler and manually stored/loaded all registers (except a7 and a6 in case of a6 stack frames ) but there is a problem with A6 stack frames checkbox (each situation needs different instructions and handling). I cannot find out how to check actual value of a6frames checkbox and compile corresponding code lines.
Does anyone know how to solve this problem (rewrite interrupt handlers not to call any function is not possible) and force compiler store all (desired) registers in interrupt handlers?
Solved! Go to Solution.
In CodeWarrior, A0,A1,D0,D1, and D2 are scratch registers. Normal functions do not save them. Interrupt functions save all scratch registers (if they call other functions).
Any function which uses a non-scratch register must save and restore it. A7 (stack pointer) and A6 (frame pointer) are preserved by being used in the normal way.
In m68k-elf-gcc, D2 is not a scratch register.
The compilers actually get this right, so if registers get clobbered, it may be due to assembly code which breaks the rules. For example, freescale distributes an "asm exception handler" that does not preserve D2. They get away with it only because the interrrupt never returns (it eventually halts).
In CodeWarrior, A0,A1,D0,D1, and D2 are scratch registers. Normal functions do not save them. Interrupt functions save all scratch registers (if they call other functions).
Any function which uses a non-scratch register must save and restore it. A7 (stack pointer) and A6 (frame pointer) are preserved by being used in the normal way.
In m68k-elf-gcc, D2 is not a scratch register.
The compilers actually get this right, so if registers get clobbered, it may be due to assembly code which breaks the rules. For example, freescale distributes an "asm exception handler" that does not preserve D2. They get away with it only because the interrrupt never returns (it eventually halts).
Hi,
The routine is quite long, so I cannot place it here. But registers d0-d2/a0-a1 are stored in the interrupt routines only, no other function stores them.
"The compilers actually get this right, so if registers get clobbered, it may be due to assembly code which breaks the rules."
Thanks for advice. When it comes to assembler I will have to investigate all the subroutines which could be called from interrupt. There should be no assembler at all but I might have overlooked something.
I have found one C function which uses registers otside scratch registers without storing them and restoring at the end. Registers are not used before/after calling of the function - so no parameters are passed to/from them. The assembler code looks a bit strange, there is a jump somewhere before these instructions and I have no idea what it means.
; 307: void FilterLength( const unsigned char typ, int* lA, int* lB )
; 308: {
0x00000000 0x4E560000 link a6,#0
; 310: switch ( typ )
; 311: {
; 312: case IIR_FILTER_ACCELERATION_p0063:
; 313: {
0x00000004 0x7400 moveq #0,d2
0x00000006 0x142E0008 move.b 8(a6),d2
0x0000000A 0x7204 moveq #4,d1
0x0000000C 0xB481 cmp.l d1,d2
0x0000000E 0x6E62 bgt.s *+100 ; 0x00000072
0x00000010 0x7000 moveq #0,d0
0x00000012 0xB480 cmp.l d0,d2
0x00000014 0x6D5C blt.s *+94 ; 0x00000072
0x00000016 0x343B2A08 move.w (8,pc,d2.l*2),d2
0x0000001A 0x48C2 ext.l d2
0x0000001C 0x4EFB2A00 jmp (pc,d2.l*2)
0x00000020 0x0006000D ori.b #0xd,d6 ; '.'
0x00000024 0x0014001B ori.b #0x1b,(a4) ; '.'
0x00000028 0x0022206E ori.b #0x6e,-(a2) ; 'n'
; 314: *lA = IIR_FILTER_ACCELRATION_LENp0063;
0x0000002C 0x000A2081 ori.b #0x81,a2 ; '.'
; 315: *lB = IIR_FILTER_ACCELRATION_LENp0063;
0x00000030 0x206E0010 movea.l 16(a6),a0
0x00000034 0x2081 move.l d1,(a0)
; 316: break;
; 317: }
; 318: case IIR_FILTER_ACCELERATION_p0125:
; 319: {
0x00000036 0x6046 bra.s *+72 ; 0x0000007e
; 320: *lA = IIR_FILTER_ACCELRATION_LENp0125;
0x00000038 0x206E000A movea.l 10(a6),a0
0x0000003C 0x2081 move.l d1,(a0)
; 321: *lB = IIR_FILTER_ACCELRATION_LENp0125;
0x0000003E 0x206E0010 movea.l 16(a6),a0
0x00000042 0x2081 move.l d1,(a0)
; 322: break;
; 323: }
; 324: case IIR_FILTER_ACCELERATION_p025:
; 325: {
0x00000044 0x6038 bra.s *+58 ; 0x0000007e
; 326: *lA = IIR_FILTER_ACCELRATION_LENp025;
0x00000046 0x206E000A movea.l 10(a6),a0
0x0000004A 0x2081 move.l d1,(a0)
; 327: *lB = IIR_FILTER_ACCELRATION_LENp025;
0x0000004C 0x206E0010 movea.l 16(a6),a0
0x00000050 0x2081 move.l d1,(a0)
; 328: break;
; 329: }
; 331: case IIR_FILTER_ACCELERATION_p05:
; 332: {
0x00000052 0x602A bra.s *+44 ; 0x0000007e
; 333: *lA = IIR_FILTER_ACCELRATION_LENp05;
0x00000054 0x206E000A movea.l 10(a6),a0
0x00000058 0x2081 move.l d1,(a0)
; 334: *lB = IIR_FILTER_ACCELRATION_LENp05;
0x0000005A 0x206E0010 movea.l 16(a6),a0
0x0000005E 0x2081 move.l d1,(a0)
; 335: break;
; 336: }
; 337: case IIR_FILTER_ACCELERATION_p1HI:
; 338: {
0x00000060 0x601C bra.s *+30 ; 0x0000007e
; 339: *lA = IIR_FILTER_ACCELRATION_LENp1HI;
0x00000062 0x206E000A movea.l 10(a6),a0
0x00000066 0x7007 moveq #7,d0
0x00000068 0x2080 move.l d0,(a0)
; 340: *lB = IIR_FILTER_ACCELRATION_LENp1HI;
; 341: break;
; 342: }
; 343: default:
; 344: {
0x0000006A 0x206E0010 movea.l 16(a6),a0
0x0000006E 0x2080 move.l d0,(a0)
; 350: }
0x00000070 0x600C bra.s *+14 ; 0x0000007e
; 345: *lA = 0;
0x00000072 0x206E000A movea.l 10(a6),a0
0x00000076 0x4290 clr.l (a0)
; 346: *lB = 0;
; 347: return;
; 348: }
; 349: }
0x00000078 0x206E0010 movea.l 16(a6),a0
0x0000007C 0x4290 clr.l (a0)
; 350: }
0x0000007E 0x4E5E unlk a6
0x00000080 0x4E75 rts
"jmp (pc,d2.l*2)" is a computed goto. The next four double words will eventually contain the addresses of the case labels (filled in by the linker). Your disassembler doesn't understand this and is mistakenly showing them as "ori" instructions. The function changes only scratch registers and appears to be compiled correctly.
Note that the stack is used for saving registers, frame pointers, return addresses, and local variables. If a local variable is an array, or some code takes a pointer to a local, there could be a buffer overflow or other undesired damage to the stack contents that could look like a register being clobberred.
Hi,
Finally I found several lines with mac operations and so I stored mac registers too. Everything seems to be OK now. Thanks for help and comments.
Hi
I don't use the 5212 and so can't exclude something special with it (Codewarrior questions are better posted in the Codewarrior forum: http://forums.freescale.com/freescale/board?board.id=CWCOMM).
But I believe that it is generally not necessary for the interrupt handler to store any more registers than it actually uses itself. If there are sub-routines called from it, it is the sub-routines that need to store any registers that will be used there.
Taking a look at an example interrupt handler and a called routine I get the following (on a V2 device):
static __interrupt__ void _Interrupt(void)
{
0x00000000 0x40E746FC2700 strldsr #0x2700
0x00000006 0x4FEFFFDC lea -36(a7),a7
0x0000000A 0x48EF43870008 movem.l d0-d2/d7/a0-a1/a6,8(a7)
..
0x00000092 0x7204 moveq #4,d1
0x00000094 0x4EB900000000 jsr _subroutine_x
subroutine_x(_ACTIVATE);
...
0x000001F6 0x4CEF43870008 movem.l 8(a7),d0-d2/d7/a0-a1/a6
0x000001FC 0x4FEF0028 lea 40(a7),a7
0x00000200 0x4E73 rte
}
In the subroutine:
extern void subroutine_x(unsigned char ucSetState)
{
0x00000000 0x4FEFFFEC lea -20(a7),a7
0x00000004 0x48D750E0 movem.l d5-d7/a4/a6,(a7)
0x00000008 0x2E01 move.l d1,d7
...
0x0000007C 0x4CD750E0 movem.l (a7),d5-d7/a4/a6
0x00000080 0x4FEF0014 lea 20(a7),a7
0x00000084 0x4E75 rts
}
Here the subroutine is first saving all of the registers that it will use too. On exit it then restores them. The interrupt also restores the ones that it used and so the interrupted code should be able to continue with a full set of registers with the same content as before the interrupt occurred.
If your registers do not have the content that they had when entering the interrupt the problem is more likely to be in the subroutine - an interrupt handler which saves all registers may mask a problem elsewhere and is doing unnecessary extra work. If the interrupt subroutine's disassembled code shows that it is indeed saving and restoring correctly (use right click on particular file in Codewarrior and then command disassemble to show disassembly of a particular file) it is more likely to be that the subroutine is corrupting the stack where the registers were saved to. Another possible problem is that the subroutine is being called with an incorrect prototype (check that the C/C++ language setting is with "Require Function Prototypes" to exclude this being possible) which could also cause corruption if the wrong registers are used.
Finally check that both files are compiled with the same compiler settings - if libraries are involved this is especially relevant - to ensure that they are using the same register passing conventions (which again could leave a particular register in a unrestored condition if mismatching).
Regards
Mark
Hi,
The problem is that the subroutine does not store registers it uses. In fact from that subroutine there are more subroutines called. None of them stores its registers. I tried to set "Require Function Prototypes" to checked but it did not help. All the routines are written in C language (no assembler). And I am using no libraries and no pragmas to change options of compiler in the sourcecodes.
Situation is as follows:
timer.c:
extern void TimerInterrupt(void);
declspec(interrupt) void Timer_INT(void)
{
// uses and stores d0-d2/a0-a1
PCSR0|= 0x0004;
TimerInterrupt();// routine+subroutines (entirely written in C) use but do not store d0-d3,d6,d7/a0-a3
// restores d0-d2/a0-a1
}
And I cannot force compiler to store registers in all subroutines called from the interrupt.
The easiest way is to store d0-d7/a0-a6 or a5 and restore in the end. I can do it eg. by:
asm void Timer_INT(void) // without a6 stack frames, would be a bit different with them
{
strldsr #0x2700
lea -60(a7),a7
movem.l d0-d7/a0-a6,(a7)
jsr TimerInterruptOrig // original Timer_INT without declspec(interrupt)
movem.l (a7),d0-d7/a0-a6
lea 64(a7),a7
rte
}
This safely stores ant restores all the registers (except mac regs). The problem is that I am unable to check a6 stack frames presence and compile correspondent verison of asm Timer_INT.
Maybe I did something wrong that subroutines do not store registers but I do not know what it was .
Hi
Can you show your subroutine which doesn't save registers: as C-code and as disassembled code (eg. by attaching the two files or cut outs of the relevant parts)?
Does this happen with routines not called from interrupts?
Does this happen when the routine is 'also' called from code which is not interrupt code?
Regards
Mark