feat(interrupts): intercept S-EL0 SP direct response message
Since FF-A v1.1 EAC0 spec only allows signaling a virtual secure
interrupt to an S-EL0 SP when in WAITING state, SPMC intercepts
a direct response message from the S-EL0 partition to proactively
signal the pending virtual secure interrupt and resume the vCPU
of the S-EL0 partition.
Further, once the secure interrupt is handled, SPMC resumes the
direct response message from current S-EL0 partition and takes
care of unwinding the call chain.
Change-Id: I55da462b5b6b7813f09a7b57c16fcd378972ac3a
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/inc/hf/api.h b/inc/hf/api.h
index 929fe52..9c9b386 100644
--- a/inc/hf/api.h
+++ b/inc/hf/api.h
@@ -133,3 +133,8 @@
struct ffa_value api_ffa_console_log(const struct ffa_value args,
struct vcpu *current);
+
+void api_ffa_resume_direct_resp_target(struct vcpu *current, struct vcpu **next,
+ ffa_vm_id_t receiver_vm_id,
+ struct ffa_value to_ret,
+ bool is_nwd_call_chain);
diff --git a/inc/hf/arch/plat/ffa.h b/inc/hf/arch/plat/ffa.h
index a530af1..1154587 100644
--- a/inc/hf/arch/plat/ffa.h
+++ b/inc/hf/arch/plat/ffa.h
@@ -330,3 +330,8 @@
struct ffa_value plat_ffa_other_world_mem_reclaim(
struct vm *to, ffa_memory_handle_t handle,
ffa_memory_region_flags_t flags, struct mpool *page_pool);
+
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+ struct vcpu **next,
+ struct ffa_value to_ret,
+ struct ffa_value *signal_interrupt);
diff --git a/inc/hf/vcpu.h b/inc/hf/vcpu.h
index 1395bcd..179863f 100644
--- a/inc/hf/vcpu.h
+++ b/inc/hf/vcpu.h
@@ -200,6 +200,15 @@
/** Partition Runtime Model. */
enum partition_runtime_model rt_model;
+
+ /**
+ * Direct response message has been intercepted to handle virtual
+ * secure interrupt for a S-EL0 partition.
+ */
+ bool direct_resp_intercepted;
+
+ /** Save direct response message args to be resumed later. */
+ struct ffa_value direct_resp_ffa_value;
};
/** Encapsulates a vCPU whose lock is held. */
diff --git a/src/api.c b/src/api.c
index 2454a40..94392c7 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2630,6 +2630,43 @@
}
/**
+ * Resume the target vCPU after the current vCPU sent a direct response.
+ * Current vCPU moves to waiting state.
+ */
+void api_ffa_resume_direct_resp_target(struct vcpu *current, struct vcpu **next,
+ ffa_vm_id_t receiver_vm_id,
+ struct ffa_value to_ret,
+ bool is_nwd_call_chain)
+{
+ if (!vm_id_is_current_world(receiver_vm_id)) {
+ *next = api_switch_to_other_world(current, to_ret,
+ VCPU_STATE_WAITING);
+
+ /* End of NWd scheduled call chain. */
+ assert(!is_nwd_call_chain ||
+ (current->call_chain.prev_node == NULL));
+ } else if (receiver_vm_id == HF_PRIMARY_VM_ID) {
+ *next = api_switch_to_primary(current, to_ret,
+ VCPU_STATE_WAITING);
+
+ /* Removing a node from NWd scheduled call chain. */
+ if (is_nwd_call_chain) {
+ vcpu_call_chain_remove_node(current, *next);
+ }
+ } else if (vm_id_is_current_world(receiver_vm_id)) {
+ /*
+ * It is expected the receiver_vm_id to be from an SP, otherwise
+ * 'plat_ffa_is_direct_response_valid' should have
+ * made function return error before getting to this point.
+ */
+ *next = api_switch_to_vm(current, to_ret, VCPU_STATE_WAITING,
+ receiver_vm_id);
+ } else {
+ panic("Invalid direct message response invocation");
+ }
+}
+
+/**
* Send an FF-A direct message response.
*/
struct ffa_value api_ffa_msg_send_direct_resp(ffa_vm_id_t sender_vm_id,
@@ -2640,6 +2677,8 @@
{
struct vcpu_locked current_locked;
enum vcpu_state next_state = VCPU_STATE_WAITING;
+ struct ffa_value signal_interrupt =
+ (struct ffa_value){.func = FFA_INTERRUPT_32};
if (!api_ffa_dir_msg_is_arg2_zero(args)) {
return ffa_error(FFA_INVALID_PARAMETERS);
@@ -2703,42 +2742,19 @@
}
}
+ if (plat_ffa_intercept_direct_response(current_locked, next, to_ret,
+ &signal_interrupt)) {
+ vcpu_unlock(¤t_locked);
+ return signal_interrupt;
+ }
+
/* Clear direct request origin for the caller. */
current->direct_request_origin_vm_id = HF_INVALID_VM_ID;
vcpu_unlock(¤t_locked);
- if (!vm_id_is_current_world(receiver_vm_id)) {
- *next = api_switch_to_other_world(
- current, to_ret,
- /*
- * Current vcpu sent a direct response. It moves to
- * waiting state.
- */
- VCPU_STATE_WAITING);
- } else if (receiver_vm_id == HF_PRIMARY_VM_ID) {
- *next = api_switch_to_primary(
- current, to_ret,
- /*
- * Current vcpu sent a direct response. It moves to
- * waiting state.
- */
- VCPU_STATE_WAITING);
- } else if (vm_id_is_current_world(receiver_vm_id)) {
- /*
- * It is expected the receiver_vm_id to be from an SP, otherwise
- * 'plat_ffa_is_direct_response_valid' should have
- * made function return error before getting to this point.
- */
- *next = api_switch_to_vm(current, to_ret,
- /*
- * current vcpu sent a direct response.
- * It moves to waiting state.
- */
- VCPU_STATE_WAITING, receiver_vm_id);
- } else {
- panic("Invalid direct message response invocation");
- }
+ api_ffa_resume_direct_resp_target(current, next, receiver_vm_id, to_ret,
+ false);
plat_ffa_unwind_call_chain_ffa_direct_resp(current, *next);
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index 10fd888..5f62591 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -493,6 +493,23 @@
(void)next;
}
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+ struct vcpu **next,
+ struct ffa_value to_ret,
+ struct ffa_value *signal_interrupt)
+{
+ /*
+ * Only applicable to SPMC as it signals virtual secure interrupt to
+ * S-EL0 partitions.
+ */
+ (void)current_locked;
+ (void)next;
+ (void)to_ret;
+ (void)signal_interrupt;
+
+ return false;
+}
+
void plat_ffa_enable_virtual_maintenance_interrupts(
struct vcpu_locked current_locked)
{
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index 483a060..4c3fd10 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -1023,6 +1023,23 @@
(void)next;
}
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+ struct vcpu **next,
+ struct ffa_value to_ret,
+ struct ffa_value *signal_interrupt)
+{
+ /*
+ * Only applicable to SPMC as it signals virtual secure interrupt to
+ * S-EL0 partitions.
+ */
+ (void)current_locked;
+ (void)next;
+ (void)to_ret;
+ (void)signal_interrupt;
+
+ return false;
+}
+
void plat_ffa_enable_virtual_maintenance_interrupts(
struct vcpu_locked current_locked)
{
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 3bd272c..010a85d 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -2040,6 +2040,134 @@
return ret;
}
+static void plat_ffa_signal_interrupt_args(struct ffa_value *args, uint32_t id)
+{
+ assert(args != NULL);
+ args->func = (uint32_t)FFA_INTERRUPT_32;
+ args->arg2 = id;
+}
+
+/**
+ * Run the vCPU in SPMC schedule mode under the runtime model for secure
+ * interrupt handling.
+ */
+static void plat_ffa_run_in_sec_interrupt_rtm(
+ struct vcpu_locked target_vcpu_locked)
+{
+ struct vcpu *target_vcpu;
+
+ target_vcpu = target_vcpu_locked.vcpu;
+
+ /* Mark the registers as unavailable now. */
+ target_vcpu->regs_available = false;
+ target_vcpu->scheduling_mode = SPMC_MODE;
+ target_vcpu->rt_model = RTM_SEC_INTERRUPT;
+ target_vcpu->state = VCPU_STATE_RUNNING;
+}
+
+/**
+ * Intercept a direct response message from current S-EL0 vCPU to signal a
+ * pending virtual secure interrupt and prepare to resume it in SPMC schedule
+ * mode under runtime model for secure interrupt handling.
+ */
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+ struct vcpu **next,
+ struct ffa_value to_ret,
+ struct ffa_value *signal_interrupt)
+{
+ bool is_el0_partition;
+ struct vcpu *current;
+
+ current = current_locked.vcpu;
+ is_el0_partition = current->vm->el0_partition;
+ assert(*next == NULL);
+
+ if (is_el0_partition && vcpu_interrupt_count_get(current_locked) > 0) {
+ dlog_verbose(
+ "Intercepting FFA_MSG_SEND_DIRECT_RESP call to "
+ "signal secure interrupt: %x\n",
+ current->vm->id);
+
+ assert(current->processing_secure_interrupt);
+ current->direct_resp_intercepted = true;
+
+ /* Save direct response message args. */
+ current->direct_resp_ffa_value = to_ret;
+
+ /*
+ * Prepare to signal virtual secure interrupt to S-EL0 SP. Refer
+ * to FF-A v1.1 EAC0 Table 8.1 case 1 and Table 12.10.
+ */
+ plat_ffa_signal_interrupt_args(
+ signal_interrupt, current->current_sec_interrupt_id);
+
+ /*
+ * Prepare to resume this partition's vCPU in SPMC schedule
+ * mode to handle virtual secure interrupt.
+ */
+ plat_ffa_run_in_sec_interrupt_rtm(current_locked);
+
+ /* Resume current vCPU. */
+ *next = NULL;
+
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * First, all the fields related to secure interrupt handling are reset and
+ * SPMC scheduled call chain is unwound.
+ * Second, the intercepted direct response message is replayed followed by
+ * unwinding of the NWd scheduled call chain.
+ */
+static struct ffa_value plat_ffa_resume_direct_response(struct vcpu *current,
+ struct vcpu **next)
+{
+ ffa_vm_id_t receiver_vm_id;
+ struct ffa_value to_ret;
+ struct vcpu_locked current_vcpu_locked;
+
+ /* Lock current vCPU. */
+ current_vcpu_locked = vcpu_lock(current);
+
+ /* Reset the fields tracking secure interrupt processing. */
+ plat_ffa_reset_secure_interrupt_flags(current_vcpu_locked);
+
+ /* SPMC scheduled call chain is completely unwound. */
+ plat_ffa_exit_spmc_schedule_mode(current_vcpu_locked);
+
+ /* Restore interrupt priority mask. */
+ plat_interrupts_set_priority_mask(current->priority_mask);
+
+ /* Replay the direct response message. */
+ receiver_vm_id = current->direct_request_origin_vm_id;
+ to_ret = current->direct_resp_ffa_value;
+
+ /* Reset the flag now. */
+ current->direct_resp_intercepted = false;
+
+ dlog_verbose(
+ "Resuming intercepted direct response from: %x to: "
+ "%x\n",
+ current->vm->id, receiver_vm_id);
+
+ /* Clear direct request origin for the caller. */
+ current->direct_request_origin_vm_id = HF_INVALID_VM_ID;
+ vcpu_unlock(¤t_vcpu_locked);
+
+ api_ffa_resume_direct_resp_target(current, next, receiver_vm_id, to_ret,
+ true);
+
+ /* Unmask non secure interrupts. */
+ if (current->present_action_ns_interrupts == NS_ACTION_QUEUED) {
+ plat_interrupts_set_priority_mask(current->mask_ns_interrupts);
+ }
+
+ return (struct ffa_value){.func = FFA_MSG_WAIT_32};
+}
+
/**
* The invocation of FFA_MSG_WAIT at secure virtual FF-A instance is compliant
* with FF-A v1.1 EAC0 specification. It only performs the state transition
@@ -2083,6 +2211,11 @@
*/
assert(!current->implicit_completion_signal);
+ if (current->direct_resp_intercepted) {
+ assert(current->vm->el0_partition);
+ return plat_ffa_resume_direct_response(current, next);
+ }
+
/* Secure interrupt pre-empted normal world. */
if (current->preempted_vcpu->vm->id == HF_OTHER_WORLD_ID) {
return plat_ffa_normal_world_resume(current, next);
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index 969622d..f197f2f 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -466,6 +466,23 @@
(void)next;
}
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+ struct vcpu **next,
+ struct ffa_value to_ret,
+ struct ffa_value *signal_interrupt)
+{
+ /*
+ * Only applicable to SPMC as it signals virtual secure interrupt to
+ * S-EL0 partitions.
+ */
+ (void)current_locked;
+ (void)next;
+ (void)to_ret;
+ (void)signal_interrupt;
+
+ return false;
+}
+
void plat_ffa_enable_virtual_maintenance_interrupts(
struct vcpu_locked current_locked)
{