@@ -562,6 +562,52 @@ END(efi_xen_start)
#endif /* CONFIG_ARM_EFI */
#ifdef CONFIG_SYSTEM_SUSPEND
+/*
+ * int32_t hyp_suspend(struct cpu_context *ptr)
+ *
+ * x0 - pointer to the storage where callee's context will be saved
+ *
+ * CPU context saved here will be restored on resume in hyp_resume function.
+ * hyp_suspend shall return a non-zero value. Upon restoring context
+ * hyp_resume shall return value zero instead. From C code that invokes
+ * hyp_suspend, the return value is interpreted to determine whether the context
+ * is saved (hyp_suspend) or restored (hyp_resume).
+ */
+FUNC(hyp_suspend)
+ /* Store callee-saved registers */
+ stp x19, x20, [x0], #16
+ stp x21, x22, [x0], #16
+ stp x23, x24, [x0], #16
+ stp x25, x26, [x0], #16
+ stp x27, x28, [x0], #16
+ stp x29, lr, [x0], #16
+
+ /* Store stack-pointer */
+ mov x2, sp
+ str x2, [x0], #8
+
+ /* Store system control registers */
+ mrs x2, VBAR_EL2
+ str x2, [x0], #8
+ mrs x2, VTCR_EL2
+ str x2, [x0], #8
+ mrs x2, VTTBR_EL2
+ str x2, [x0], #8
+ mrs x2, TPIDR_EL2
+ str x2, [x0], #8
+ mrs x2, MDCR_EL2
+ str x2, [x0], #8
+ mrs x2, HSTR_EL2
+ str x2, [x0], #8
+ mrs x2, CPTR_EL2
+ str x2, [x0], #8
+ mrs x2, HCR_EL2
+ str x2, [x0], #8
+
+ /* hyp_suspend must return a non-zero value */
+ mov x0, #1
+ ret
+END(hyp_suspend)
FUNC(hyp_resume)
msr DAIFSet, 0xf /* Disable all interrupts */
@@ -587,7 +633,47 @@ FUNC(hyp_resume)
b enable_secondary_cpu_mm
mmu_resumed:
- b .
+ /* Now we can access the cpu_context, so restore the context here */
+ ldr x0, =cpu_context
+
+ /* Restore callee-saved registers */
+ ldp x19, x20, [x0], #16
+ ldp x21, x22, [x0], #16
+ ldp x23, x24, [x0], #16
+ ldp x25, x26, [x0], #16
+ ldp x27, x28, [x0], #16
+ ldp x29, lr, [x0], #16
+
+ /* Restore stack pointer */
+ ldr x2, [x0], #8
+ mov sp, x2
+
+ /* Restore system control registers */
+ ldr x2, [x0], #8
+ msr VBAR_EL2, x2
+ ldr x2, [x0], #8
+ msr VTCR_EL2, x2
+ ldr x2, [x0], #8
+ msr VTTBR_EL2, x2
+ ldr x2, [x0], #8
+ msr TPIDR_EL2, x2
+ ldr x2, [x0], #8
+ msr MDCR_EL2, x2
+ ldr x2, [x0], #8
+ msr HSTR_EL2, x2
+ ldr x2, [x0], #8
+ msr CPTR_EL2, x2
+ ldr x2, [x0], #8
+ msr HCR_EL2, x2
+ isb
+
+ /* Since context is restored return from this function will appear as
+ * return from hyp_suspend. To distinguish a return from hyp_suspend
+ * which is called upon finalizing the suspend, as opposed to return
+ * from this function which executes on resume, we need to return zero
+ * value here. */
+ mov x0, #0
+ ret
END(hyp_resume)
#endif /* CONFIG_SYSTEM_SUSPEND */
@@ -3,8 +3,30 @@
#ifndef __ASM_ARM_SUSPEND_H__
#define __ASM_ARM_SUSPEND_H__
+#ifdef CONFIG_ARM_64
+struct cpu_context {
+ uint64_t callee_regs[12];
+ uint64_t sp;
+ uint64_t vbar_el2;
+ uint64_t vtcr_el2;
+ uint64_t vttbr_el2;
+ uint64_t tpidr_el2;
+ uint64_t mdcr_el2;
+ uint64_t hstr_el2;
+ uint64_t cptr_el2;
+ uint64_t hcr_el2;
+} __aligned(16);
+#else
+struct cpu_context {
+ uint8_t pad;
+};
+#endif
+
+extern struct cpu_context cpu_context;
+
int32_t domain_suspend(register_t epoint, register_t cid);
void hyp_resume(void);
+int32_t hyp_suspend(struct cpu_context *ptr);
#endif
@@ -11,6 +11,8 @@
#include <asm/platform.h>
#include <public/sched.h>
+struct cpu_context cpu_context;
+
/* Reset values of VCPU architecture specific registers */
static void vcpu_arch_reset(struct vcpu *v)
{
@@ -170,9 +172,23 @@ static long system_suspend(void *data)
*/
update_boot_mapping(true);
- status = call_psci_system_suspend();
- if ( status )
- dprintk(XENLOG_ERR, "PSCI system suspend failed, err=%d\n", status);
+ if ( hyp_suspend(&cpu_context) )
+ {
+ status = call_psci_system_suspend();
+ /*
+ * If suspend is finalized properly by above system suspend PSCI call,
+ * the code below in this 'if' branch will never execute. Execution
+ * will continue from hyp_resume which is the hypervisor's resume point.
+ * In hyp_resume CPU context will be restored and since link-register is
+ * restored as well, it will appear to return from hyp_suspend. The
+ * difference in returning from hyp_suspend on system suspend versus
+ * resume is in function's return value: on suspend, the return value is
+ * a non-zero value, on resume it is zero. That is why the control flow
+ * will not re-enter this 'if' branch on resume.
+ */
+ if ( status )
+ dprintk(XENLOG_ERR, "PSCI system suspend failed, err=%d\n", status);
+ }
system_state = SYS_STATE_resume;
update_boot_mapping(false);