I am developing a bare-metal ARM project using LPC2138 soc. I have written an IRQ interrupt handler for I2C. But it does not return properly. The handler is being called repeatedly for a single interrupt.
I have done a detailed debug analysis of this issue.
In the ARM7TDMI reference manual it's clearly mentioned the following thing.
But when I disassembled the code, I found that the code generated by GCC does not restore the CPSR resgister. Also an unknown value at last.
I have declared the IRQ handler like the following
void I2C0_IRQ_handler(void) __attribute__ ((interrupt("IRQ")));
CFLAGS := -mcpu=arm7tdmi-s -g3 -Wall -I. -gdwarf-2
AS_FLAGS := -mcpu=arm7tdmi-s -g3 -gdwarf-2
LD_FLAGS := -Wl,-Map,$(TARGET:%.hex=%).map -nostartfiles
Your analysis has two fatal flaws: Firstly, you're looking at it from the perspective of the CPU core, and secondly, it's wrong.
Second flaw first, I'm not sure why the ARM7TDMI manual has such an oddly overly-specific wording (you can't realistically do all those things separately anyway) but all 3 aspects are performed by the
subs pc, lr, #4 line in your code.
subs pc, lr is specifically an exception return instruction - it atomically restores the CPSR from the SPSR and returns to the fixed-up LR address. Since the CPU's interrupt disable flags are in the CPSR, then assuming that the I bit in that SPSR value is clear (which you kind of expect, given that you took an IRQ to get here...), IRQs will also be unmasked automatically in the process.
Thus, from the CPU core's point of view, your code is already (by virtue of its final instruction) doing everything an exception handler needs to, i.e. it resumes execution at the same point and in the same state as when the IRQ happened - there's certainly nothing wrong with the compiler. What it doesn't do, though, is anything about the fact that the peripheral way beyond the core is still asserting its interrupt, therefore the next thing you do after returning from the IRQ is to happily take the same IRQ again immediately. Repeat ad infinitum.
You have at least gone beyond the core as far as the interrupt controller; writing to
VICVectAddr acknowledges the interrupt at the VIC, but that merely frees the VIC itself up to prioritise any pending interrupts and deliver the next one, which since the external source is still asserted (and assuming no higher-priority IRQ has come in from elsewhere), is still the same one.
To actually handle the interrupt and make any progress, you need to service the I2C peripheral raising it. Section 13.9 of the LPC213x manual outlines what needs to be done for each interrupt condition, but in terms of clearing the asserted interrupt, you need to write the SIC field of I2C0CONCLR.