SDEI: Add API for explicit dispatch

This allows for other EL3 components to schedule an SDEI event dispatch
to Normal world upon the next ERET. The API usage constrains are set out
in the SDEI dispatcher documentation.

Documentation to follow.

Change-Id: Id534bae0fd85afc94523490098c81f85c4e8f019
Signed-off-by: Jeenu Viswambharan <jeenu.viswambharan@arm.com>
diff --git a/services/std_svc/sdei/sdei_intr_mgmt.c b/services/std_svc/sdei/sdei_intr_mgmt.c
index d7cf289..4551a8b 100644
--- a/services/std_svc/sdei/sdei_intr_mgmt.c
+++ b/services/std_svc/sdei/sdei_intr_mgmt.c
@@ -465,6 +465,85 @@
 	return 0;
 }
 
+/* Explicitly dispatch the given SDEI event */
+int sdei_dispatch_event(int ev_num, unsigned int preempted_sec_state)
+{
+	sdei_entry_t *se;
+	sdei_ev_map_t *map;
+	cpu_context_t *ctx;
+	sdei_dispatch_context_t *disp_ctx;
+	sdei_cpu_state_t *state;
+
+	/* Validate preempted security state */
+	if ((preempted_sec_state != SECURE) || (preempted_sec_state != NON_SECURE))
+		return -1;
+
+	/* Can't dispatch if events are masked on this PE */
+	state = sdei_get_this_pe_state();
+	if (state->pe_masked == PE_MASKED)
+		return -1;
+
+	/* Event 0 can't be dispatched */
+	if (ev_num == SDEI_EVENT_0)
+		return -1;
+
+	/* Locate mapping corresponding to this event */
+	map = find_event_map(ev_num);
+	if (!map)
+		return -1;
+
+	/*
+	 * Statically-bound or dynamic maps are dispatched only as a result of
+	 * interrupt, and not upon explicit request.
+	 */
+	if (is_map_dynamic(map) || is_map_bound(map))
+		return -1;
+
+	/* The event must be private */
+	if (is_event_shared(map))
+		return -1;
+
+	/* Examine state of dispatch stack */
+	disp_ctx = get_outstanding_dispatch();
+	if (disp_ctx) {
+		/*
+		 * There's an outstanding dispatch. If the outstanding dispatch
+		 * is critical, no more dispatches are possible.
+		 */
+		if (is_event_critical(disp_ctx->map))
+			return -1;
+
+		/*
+		 * If the outstanding dispatch is Normal, only critical events
+		 * can be dispatched.
+		 */
+		if (is_event_normal(map))
+			return -1;
+	}
+
+	se = get_event_entry(map);
+	if (!can_sdei_state_trans(se, DO_DISPATCH))
+		return -1;
+
+	/* Activate the priority corresponding to the event being dispatched */
+	ehf_activate_priority(sdei_event_priority(map));
+
+	/*
+	 * We assume the current context is SECURE, and that it's already been
+	 * saved.
+	 */
+	ctx = restore_and_resume_ns_context();
+
+	/*
+	 * The caller has effectively terminated execution. Record to resume the
+	 * preempted context later when the event completes or
+	 * complete-and-resumes.
+	 */
+	setup_ns_dispatch(map, se, ctx, preempted_sec_state, 0);
+
+	return 0;
+}
+
 int sdei_event_complete(int resume, uint64_t pc)
 {
 	sdei_dispatch_context_t *disp_ctx;
@@ -556,6 +635,13 @@
 		 * interrupt.
 		 */
 		plat_ic_end_of_interrupt(disp_ctx->intr_raw);
+	} else {
+		/*
+		 * An unbound event must have been dispatched explicitly.
+		 * Deactivate the priority level that was activated at the time
+		 * of explicit dispatch.
+		 */
+		ehf_deactivate_priority(sdei_event_priority(map));
 	}
 
 	if (is_event_shared(map))