fix(cpus): correct CVE-2025-11689/0647 combined trap handler on N2

The combined WORKAROUND_CVE_2025_0647 && CVE_2025_11689_DYNAMIC_WA path
in neoverse_n2_impl_defined_el3_handler had several porting bugs that
caused VM hangs and register corruption on Neoverse N2 (CN103/CN105):

- bl->b: handler used bl to tail-call workaround functions, clobbering
  x30 and causing execution to fall into neoverse_n2_reset_func.
- SP misuse: wa_cve_2025_0647_do_cpp_wa requires SP==CTX_BASE but was
  called from C, where SP is offset by the C frame. Refactored
  handle_tcr_or_cpp_exception to return 0/1 so the assembly dispatcher
  can tail-call it directly with SP restored to CTX_BASE.
- Register save: ea_delegate.S only saved x0-x5; the C handler clobbered
  x6-x18, corrupting guest registers on ERET. Extended save/restore to
  cover x0-x29 for the CVE_2025_11689_DYNAMIC_WA fast path.

Signed-off-by: Suman Ghosh <sumang@marvell.com>
diff --git a/bl31/aarch64/ea_delegate.S b/bl31/aarch64/ea_delegate.S
index 835ba7f..1b74504 100644
--- a/bl31/aarch64/ea_delegate.S
+++ b/bl31/aarch64/ea_delegate.S
@@ -67,6 +67,27 @@
 	stp	x0, x1, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X0]
 	stp	x2, x3, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X2]
 	stp	x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4]
+#if CVE_2025_11689_DYNAMIC_WA
+	/*
+	 * The CVE-2025-11689 IMP_DEF handler calls C code (including printf
+	 * for debug) which clobbers x6-x18.  x19-x29 are callee-saved by C
+	 * but the assembly dispatch in neoverse_n2_impl_defined_el3_handler
+	 * may also clobber them.  Save the full set here so that ea_delegate
+	 * and the cpp_wa tail-call path can restore all victim registers.
+	 */
+	stp	x6, x7,   [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]
+	stp	x8, x9,   [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8]
+	stp	x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10]
+	stp	x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12]
+	stp	x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14]
+	stp	x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
+	stp	x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18]
+	stp	x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20]
+	stp	x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22]
+	stp	x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24]
+	stp	x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26]
+	stp	x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28]
+#endif /* CVE_2025_11689_DYNAMIC_WA */
 
 	/* Get the cpu_ops pointer */
 	bl	get_cpu_ops_ptr
@@ -95,6 +116,19 @@
 	add	x16, x16, #4
 	msr	elr_el3, x16
 	isb
+	/* Restore all victim GP registers (x6-x29 were saved on entry). */
+	ldp	x6, x7,   [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]
+	ldp	x8, x9,   [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8]
+	ldp	x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10]
+	ldp	x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12]
+	ldp	x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14]
+	ldp	x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
+	ldp	x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18]
+	ldp	x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20]
+	ldp	x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22]
+	ldp	x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24]
+	ldp	x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26]
+	ldp	x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28]
 	ldp	x0, x1, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X0]
 	ldp	x2, x3, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X2]
 	ldp	x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4]
diff --git a/include/lib/cpus/aarch64/cve_2025_11689.h b/include/lib/cpus/aarch64/cve_2025_11689.h
index 3050e39..8bb6678 100644
--- a/include/lib/cpus/aarch64/cve_2025_11689.h
+++ b/include/lib/cpus/aarch64/cve_2025_11689.h
@@ -26,7 +26,7 @@
 } cve_2025_11689_dynamic_wa_mode_e;
 
 void cve_2025_11689_imp_def_trap_workaround(void);
-void handle_tcr_or_cpp_exception(uint64_t flags);
+int handle_tcr_or_cpp_exception(uint64_t flags);
 void write_actlr2_el1_bit54(uint64_t set);
 void wa_cve_2025_11689_power_on_finish(void);
 void wa_cve_2025_11689_suspend_finish(void);
diff --git a/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c b/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c
index 0f91e61..b8a3408 100644
--- a/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c
+++ b/lib/cpus/aarch64/dynamic_wa_cve_2025_11689.c
@@ -185,22 +185,33 @@
 }
 
 #if WORKAROUND_CVE_2025_0647
-void handle_tcr_or_cpp_exception(uint64_t flags)
+/*
+ * Returns 0 if the TCR/VTCR workaround was applied (caller should ERET via
+ * ea_delegate.S after advancing ELR_EL3 by 4).
+ * Returns 1 if the CPP workaround is needed (caller must tail-call
+ * wa_cve_2025_0647_do_cpp_wa with SP == CTX_BASE; calling it from C would
+ * corrupt register restore since it uses SP-relative ctx loads).
+ */
+int handle_tcr_or_cpp_exception(uint64_t flags)
 {
 	uint32_t pos = plat_my_core_pos();
+	uint8_t src_el =
+		(uint8_t)((read_spsr_el3() >> SPSR_EL_SHIFT) & SPSR_EL_MASK);
 
-	if (((read_spsr_el3() >> SPSR_EL_SHIFT) & SPSR_EL_MASK) == 0U) {
-		wa_cve_2025_0647_do_cpp_wa(flags | WA_IS_TRAP_HANDLER);
-		return;
+	(void)flags;
+
+	if (src_el == 0U) {
+		return 1; /* cpp_wa for EL0 */
 	}
 
 	if (shadow_tcr_el1[pos] != read_tcr_el1() ||
 	    shadow_vtcr_el2[pos] != read_vtcr_el2()) {
 		cve_2025_11689_imp_def_trap_workaround();
-		return;
+		return 0; /* tcr_wa handled; ea_delegate.S advances ELR+4 + ERET */
 	}
 
-	wa_cve_2025_0647_do_cpp_wa(flags | WA_IS_TRAP_HANDLER);
+	/* cpp_wa for EL1/EL2: caller must tail-call wa_cve_2025_0647_do_cpp_wa */
+	return 1;
 }
 #endif /* WORKAROUND_CVE_2025_0647 */
 
diff --git a/lib/cpus/aarch64/neoverse_n2.S b/lib/cpus/aarch64/neoverse_n2.S
index fdd90e9..9d6a9ab 100644
--- a/lib/cpus/aarch64/neoverse_n2.S
+++ b/lib/cpus/aarch64/neoverse_n2.S
@@ -607,8 +607,47 @@
 func neoverse_n2_impl_defined_el3_handler
 #if IMAGE_BL31
 #if WORKAROUND_CVE_2025_0647 && CVE_2025_11689_DYNAMIC_WA
+	/*
+	 * Save the ea_delegate.S return address to a stack slot below the
+	 * cpu_context. The C function modifies SP (pushing its own frame), so
+	 * we cannot use a callee-saved register without clobbering the victim's
+	 * value.  Reserve 16 bytes (alignment required); save x30 at [sp+8].
+	 * The C function starts pushing at sp-16 (below our slot), so our
+	 * saved value is safe.  After bl returns, SP is restored by C to
+	 * ctx_base-16; we then ldr+add to get back to ctx_base before any
+	 * tail-call that needs SP == ctx_base.
+	 */
+	sub	sp, sp, #16
+	str	x30, [sp, #8]
 	mov	x0, #WA_LS_RCG_EN
 	bl	handle_tcr_or_cpp_exception
+	/* x0 == 0: tcr_wa handled; ea_delegate.S will advance ELR+4 + ERET */
+	/* x0 != 0: cpp_wa needed; tail-call with SP restored to ctx_base    */
+	cbz	x0, 1f
+	ldr	x30, [sp, #8]
+	add	sp, sp, #16
+	/* Restore x6-x29: C handler clobbered caller-saved regs; x6-x29 were
+	 * saved to the cpu_context by ea_delegate.S on exception entry.
+	 * wa_cve_2025_0647_do_cpp_wa restores x0-x5 and x30 itself.
+	 * storing/restoring x6–x18 would be sufficient from a pure C ABI
+	 * standpoint. But covering x6-x29 ensures no values are overwritten. */
+	ldp	x6, x7,   [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]
+	ldp	x8, x9,   [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8]
+	ldp	x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10]
+	ldp	x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12]
+	ldp	x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14]
+	ldp	x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
+	ldp	x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18]
+	ldp	x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20]
+	ldp	x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22]
+	ldp	x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24]
+	ldp	x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26]
+	ldp	x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28]
+	mov	x0, #(WA_LS_RCG_EN | WA_IS_TRAP_HANDLER)
+	b	wa_cve_2025_0647_do_cpp_wa
+1:	ldr	x30, [sp, #8]
+	add	sp, sp, #16
+	ret
 #elif WORKAROUND_CVE_2025_0647
 	mov	x0, #WA_LS_RCG_EN
 
@@ -618,7 +657,7 @@
 	orr	x0, x0, #WA_IS_TRAP_HANDLER
 	b	wa_cve_2025_0647_do_cpp_wa
 #elif CVE_2025_11689_DYNAMIC_WA
-	bl	cve_2025_11689_imp_def_trap_workaround
+	b	cve_2025_11689_imp_def_trap_workaround
 #endif
 #endif
 endfunc neoverse_n2_impl_defined_el3_handler