I wrote:
>> If your code is simple enough so that you can guarantee that you never call "SET_IPL(7) ; xxx ; SET_IPL(0);"
>> anywhere that the IPL isn't already zero you're OK, but that prevents you from writing code with
>> functions called from both mainline and interrupt-code.
>> Technically it doesn't stop you doing this, it just makes lots of new and nasty bugs
>
> Can you explain what you mean by this? Are you saying that,
> for example, in an ISR the IPL may be non-zero and that
> blindly resetting it to 0 will mess things up?
Yes, sure will.
The Coldfire CPUs have seven IPLs. Devices can interrupt at any level from 1 to 6, or 7 which is "non maskable" and best avoided.
This means that when everything is set up OK, a device interrupting at (say) level 6 can interrupt OTHER INTERRUPT SERVICE ROUTINES running from lower level interrupts. This can be very useful if you need it.
Other primitive ancient CPUs (cheap 8-bitters and their derivatives like the Pentium in your PC
have only one level. They can support multiple levels by faking it in the interrupt controller. This means the CPU has to program the interrupt controller with the heirarchy if multi-level nested interrupts are needed and let it know when interrupt service routines end. Whether this should be done in the CPU ir the interrupt controller is somewhat of a "religious argument", and you can see which one I like.
The CPU starts of in the "mainline" at Level zero. When a level "N" interrupt happens, the CPU's IPL is set to Level N. That means that device can't interrupt the CPU again. It is really bad news if you're in an ISR (Interrupt Service Routine), get the levels wrong and the device interrupts the SAME service routine in the middle. This is uncontrolled re-entrancy and almost always bad.
So if you're running in an interrupt service routine for a device at level "N", and have to set the level to "7" in order to mess with the mask register, it is ESSENTIAL to restore the level back to "N" and *NOT* back to Zero.
The ugly and dangerous way to do this is to have to know that a particular ISR is interrupted by a device at a specific hardwired level (say "5"), and then you do::
asm_set_ipl(7);
Change mask;
asm_set_ipl(5);
This is "ugly" as it means you can change the IPL somewhere else and forget to change it in this routine.
It also means you can't write general purpose functions that need to disable the interrupts and be called from mainline (return IPL to zero) and multiple different ISRs where the IPL has to be returned to different values. It also means that if someone cut/pastes that code into a different ISR and doesn't change the "magic numbers" it'll cause more problems.
*BUT* if you track down the source code for "asm_set_ipl()" (should be in mfcxxx.s) you should find it returns the previous level. So your code for disabling interrupts (everywhere that you disable and restore interrupts) should be:
int saved_ipl = asm_set_ipl(7);
Change the IMRs;
asm_set_ipl(saved_ipl);
If you can't find mcf5xxx.s in your distribution, then google for "_mcf5xxx_wr_other_sp" and you'll find a copy somewhere.
> For example, I have no idea how I might dump the stack in C (I realise I can see it in the debugging window though).
And when you check the debugger, where was the code when the spurious interrupt happened? That's where the bug is. That's the problem you were looking for.
Here's the simple way to dump the stack ("DEBUG" is our print function - replace with whatever you're using, and "dump_stack890' dumps frames, but you could just have it print 10 or 20 words and look for addresses manually)::
__attribute__((interrupt_handler)) static void int_addr_isr( void ){ uint32_t i[1]; unsigned int sr = i[2] & 0xffff; unsigned int fs = ( ( i[2] >> 16 ) & 0x3 ) | ( ( i[2] >> 24 ) & 0xc ); unsigned int vector = ( i[2] >> 18 ) & 0xff; unsigned int format = ( i[2] >> 28 ); unsigned int pc = i[3]; DEBUG( 1, "sr=0x%04X fs=0x%01X vector=%d format=0x%02X pc=0x%08X", sr, fs, vector, format, pc ); dump_stack( &i[1], 1 );
Note that "i" is a local variable on the stack. So the rest of the stack is "up" from the address of that variable. For each CPU you want to do this with you have to check the manuals to see what the exception stack looks like and where the PC and other things are. Since the "frame pointer" is pushed on the stack it is pretty simple to walk back through all previous stack frames, printing all the previous function addresses too. That's how the debugger does it.
But if you've got a debugger it'll do all that for you.