Cause register bug in qemu-jz
In MIPS, cause register is responsible for telling CPU which interrupt is happening. CPU read the IP bits in cause register and dispatch interrupt to interrupt service routine.
One question: who is responsible for clear IP bits in cause register? That is interrupt handler. Interrupt handler must clear the corresponding IP bit in cause register, othewise next time, interrupt is reenabled, CPU thinks the interrupt is still happening!
In linux, the function to clear IP bit in mask_and_ack_intc_irq. In JZ4740, it is:
50 static void mask_and_ack_intc_irq(unsigned int irq)
51 {
52 __intc_mask_irq(irq);
53 __intc_ack_irq(irq);
54 }
1372 #define __intc_mask_irq(n) ( REG_INTC_IMSR = (1 << (n)) )
1373 #define __intc_ack_irq(n) ( REG_INTC_IPR = (1 << (n)) )
Linux writes to REG_INTC_IPR to clear pending interrupt and JZ4740 interrupt controller will clear the interrupt signal to MIPS CPU.
In qemu-jz, when writing to REG_INTC_IPR, the interrupt to MIPS cpu is not cleared and linux runs in a dead loop.
The following is the interrupt handling process in MIPS CPU.
1. Set ECP=PC and CPU jumps to 0x80000200 and then to handle_int. The interrupt is disabled.
2. Function handle_int will save all the register(including EPC so that interrupt can be nested) to stack and call plat_irq_dispatch.
3. After the irq is processed, it will reenable the in function __do_softirq. If a new interrupt is coming at this time(check the IP bits cause register), go to 1. otherwise go to 4.
4. Call ret_from_irq and restore all the register(including EPC) and using ERET to set PC=EPC.
5. Runs from PC.
Because of the IP bits in cause register is not cleared, linux will run into a dead loop between 1 and 3.
Thanks to the log function and remote gdb. Really hard to find this bug in qemu-jz.