feat(arch timer): track pending timer configured by SP vCPU

Hafnium SPMC takes important actions while switching away/resuming the
execution context of an SP, in order to support arch timers:
1. When switching away from a vCPU, it either adds or removes the
   vCPU's entry(i.e. timer_link) from the pending timer list depending
   on whether the vCPU has enabled the timer.
2. Before resuming a vCPU, SPMC will have to track the deadline set by
   the vCPU if it did not expire. If expired, SPMC will directly inject
   the virtual interrupt and signal it at the right time.

Change-Id: I323e2fcc7f12ca3a2c296593fe091bf1d4cf8ad0
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 4d14c63..c4ca2ed 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -10,11 +10,13 @@
 
 #include "hf/arch/barriers.h"
 #include "hf/arch/gicv3.h"
+#include "hf/arch/host_timer.h"
 #include "hf/arch/init.h"
 #include "hf/arch/memcpy_trapped.h"
 #include "hf/arch/mmu.h"
 #include "hf/arch/plat/ffa.h"
 #include "hf/arch/plat/smc.h"
+#include "hf/arch/timer.h"
 #include "hf/arch/vmid_base.h"
 
 #include "hf/api.h"
@@ -26,6 +28,7 @@
 #include "hf/hf_ipi.h"
 #include "hf/panic.h"
 #include "hf/plat/interrupts.h"
+#include "hf/timer_mgmt.h"
 #include "hf/vm.h"
 #include "hf/vm_ids.h"
 
@@ -89,7 +92,17 @@
  */
 void complete_saving_state(struct vcpu *vcpu)
 {
+	host_timer_save_arch_timer(&vcpu->regs.arch_timer);
+
+	timer_vcpu_manage(vcpu);
 	api_regs_state_saved(vcpu);
+
+	/*
+	 * Since switching away from current vCPU, disable the host physical
+	 * timer for now. If necessary, the host timer will be reconfigured
+	 * at appropriate time to track timer deadline of the vCPU.
+	 */
+	host_timer_disable();
 }
 
 /**
@@ -97,7 +110,16 @@
  */
 void begin_restoring_state(struct vcpu *vcpu)
 {
-	(void)vcpu;
+	/*
+	 * If a vCPU's timer has expired while it was de-scheduled, SPMC will
+	 * inject the virtual timer interrupt before resuming the vCPU.
+	 * If not, there is a live state and we need to configure the host timer
+	 * to track it again.
+	 */
+	if (arch_timer_enabled(&vcpu->regs) &&
+	    (arch_timer_remaining_ns(&vcpu->regs) != 0)) {
+		host_timer_track_deadline(&vcpu->regs.arch_timer);
+	}
 }
 
 /**
diff --git a/src/arch/aarch64/hypervisor/host_timer.c b/src/arch/aarch64/hypervisor/host_timer.c
index c7273c8..b7b243a 100644
--- a/src/arch/aarch64/hypervisor/host_timer.c
+++ b/src/arch/aarch64/hypervisor/host_timer.c
@@ -51,3 +51,40 @@
 	plat_interrupts_configure_interrupt(int_desc);
 #endif
 }
+
+/**
+ * Save the arch timer state being tracked by host timer.
+ */
+void host_timer_save_arch_timer(struct timer_state *timer)
+{
+#if SECURE_WORLD == 1
+	timer->cval = read_msr(MSR_CNTHPS_CVAL_EL2);
+	timer->ctl = read_msr(MSR_CNTHPS_CTL_EL2);
+#else
+	timer->cval = read_msr(MSR_CNTHP_CVAL_EL2);
+	timer->ctl = read_msr(MSR_CNTHP_CTL_EL2);
+#endif
+}
+
+/**
+ * Configure the host timer to track the arch timer deadline.
+ */
+void host_timer_track_deadline(struct timer_state *timer)
+{
+	/*
+	 * Clear timer control register before restoring compare value, to avoid
+	 * a spurious timer interrupt. This could be a problem if the interrupt
+	 * is configured as edge-triggered, as it would then be latched in.
+	 */
+#if SECURE_WORLD == 1
+	write_msr(cnthps_ctl_el2, 0);
+	write_msr(cnthps_cval_el2, timer->cval);
+	write_msr(cnthps_ctl_el2, timer->ctl);
+#else
+	write_msr(cnthp_ctl_el2, 0);
+	write_msr(cnthp_cval_el2, timer->cval);
+	write_msr(cnthp_ctl_el2, timer->ctl);
+#endif
+	/* Ensure that the write to ctl register has taken effect. */
+	isb();
+}