ARMv7 exception return based task switch failure (Krait 400, SVC mode tasks)

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Vx0id
Posts: 1
Joined: Sat Sep 26, 2026 9:35 pm

ARMv7 exception return based task switch failure (Krait 400, SVC mode tasks)

Post by Vx0id »

Hi,

I am writing my own hobby OS.

The hardware I am using is as follows:
Samsung Galaxy S5, Snapdragon 801 (MSM8974AC), Krait 400 cores, ARMv7-A. The MMU is on but only as a flat identity map (no per task address spaces, no user mode). Bare metal, SVC mode only kernel tasks. I am booting it from lk2nd, not stock android. This OS is not based on AOSP or linux at all.

The issue I am having specifically:
When I dispatch a task with an exception return (rfefd sp!, and separately confirmed identical with a manual msr spsr_cxsf + movs pc, lr two instruction return) the task never seems to run. The frame's PC and SPSR are verified byte correct via serial trace immediately before the return instruction executes, but the very next timer interrupt shows the task's stack pointer completely unchanged from its pre dispatch value, meaning not even the first instruction's stack adjusting prologue ran.

The part I think matters most:
If I force the I bit set in the SPSR that gets restored (0x93, SVC with IRQs masked) the task runs fine. If I restore the natural SPSR (0x13, SVC with IRQs enabled) it fails. In that case UART output stops after exactly 3 timer ticks (GIC int id 0x28, the memory mapped QTMR frame) and no undefined instruction, prefetch abort or data abort marker is ever printed.

What I have ruled out:
- ICIALLU, BPIALL and icache/branch predictor invalidation after writing the frame
- three different return instruction encodings (rfefd, msr spsr + movs pc, ldmfd with the caret)
- srsdb entry versus a manual mrs spsr / msr cpsr_c mode switch like lk2nd's arm_irq and linux's svc_entry
- GIC edge/level config for the timer SPI (confirmed edge triggered) and the clear before EOI ordering
- the GIC/timer/EOI sequence itself (confirmed working via round robin cycling among other tasks once dispatched via a manual bypass)
- frame memory itself (plain reads and writes work when I test directly)
- stack balance in the interrupt handler

Other things that you should know:
- A real task dispatched on a plain scratch stack works, the same task on my per task stack array does not. Plain data reads and writes to that array are fine.
- QTMR CNTP_CTL reads 0x5 (ISTATUS already set) right after timer init, before the first tick.
- The GIC needed the TrustZone fix (all GICD_IGROUPR set to group 1, GICD_CTLR=3, GICC_CTLR=0xF) before any interrupt got through.

Has anyone seen an interrupt get taken again on the very first instruction after an exception return on Krait / QGIC? If you need more information please don't hesitate to reach out and ask. I will do my best to answer.

Thanks
Post Reply