SPM: Use signals to drive FFM API procedures

TFM_IPC_REPLY_SIGNAL was created to replace wait_obj to unify the
replied status and signals. After the waiting logic is converted
into signal-oriented, the existing synchronous API calling mechanism
such as setting thread status and injecting return values can be
updated. The synchronous mechanism concludes functions and data to
be called into one context, big critical sections need to be
available when calling to ensure there is no data inconsistency.
Using signals to communicate between FF-M API layer and thread
management can separate the contexts so big critical sections
can be avoided.

The new APIs in FFM API layer, backend_assert_signal() and
backend_wait_signals() which replace backend_wake_up() and
backend_wait() just set the signals in partition and do not change
the status of the thread now. Scheduler now manages threads by
querying the signal state in the partition with an assigned
callback. The scheduler runs in an individual context provided by
PendSV mode.

The partition behaviours when scheduler queries the state are:

 - If any waiting signal is asserted, the thread is required to
   return the context and run.
 - If NONE of waiting signals is asserted, it means the thread is
   requested to block.

The context return process is in thrd_next() according to the
THRD_STATE_RET_VAL_AVAIL to avoid the data operation in FFM.

Signed-off-by: Jianliang Shen <jianliang.shen@arm.com>
Change-Id: Id9444eb3f26ecce674b6409047cb29237a80b6e8
diff --git a/secure_fw/spm/cmsis_psa/spm.h b/secure_fw/spm/cmsis_psa/spm.h
index ce30249..04015ec 100644
--- a/secure_fw/spm/cmsis_psa/spm.h
+++ b/secure_fw/spm/cmsis_psa/spm.h
@@ -110,6 +110,7 @@
     void                               *p_metadata;
     struct context_ctrl_t              ctx_ctrl;
     struct thread_t                    thrd;            /* IPC model */
+    uintptr_t                          reply_value;
 #else
     uint32_t                           state;           /* SFN model */
 #endif
@@ -347,13 +348,6 @@
 
 void update_caller_outvec_len(struct conn_handle_t *handle);
 
-/*
- * Set partition signal.
- *
- * Assert a signal to given partition.
- */
-void spm_assert_signal(void *p_pt, psa_signal_t signal);
-
 #if CONFIG_TFM_PSA_API_CROSS_CALL == 1
 
 /*
diff --git a/secure_fw/spm/cmsis_psa/spm_ipc.c b/secure_fw/spm/cmsis_psa/spm_ipc.c
index 7bae3da..74f7a51 100755
--- a/secure_fw/spm/cmsis_psa/spm_ipc.c
+++ b/secure_fw/spm/cmsis_psa/spm_ipc.c
@@ -505,23 +505,3 @@
         handle->caller_outvec[i].len = handle->outvec[i].len;
     }
 }
-
-void spm_assert_signal(void *p_pt, psa_signal_t signal)
-{
-    struct critical_section_t cs_assert = CRITICAL_SECTION_STATIC_INIT;
-    struct partition_t *partition = (struct partition_t *)p_pt;
-
-    if (!partition) {
-        tfm_core_panic();
-    }
-
-    CRITICAL_SECTION_ENTER(cs_assert);
-
-    partition->signals_asserted |= signal;
-
-    if (partition->signals_waiting & signal) {
-        backend_wake_up(partition);
-    }
-
-    CRITICAL_SECTION_LEAVE(cs_assert);
-}
diff --git a/secure_fw/spm/cmsis_psa/thread.c b/secure_fw/spm/cmsis_psa/thread.c
index 32df2cb..58ea9ca 100644
--- a/secure_fw/spm/cmsis_psa/thread.c
+++ b/secure_fw/spm/cmsis_psa/thread.c
@@ -21,14 +21,36 @@
 #define LIST_HEAD   p_thrd_head
 #define RNBL_HEAD   p_rnbl_head
 
+/* Callback function pointer for thread to query current state. */
+static thrd_query_state_t query_state_cb = (thrd_query_state_t)NULL;
+
+void thrd_set_query_callback(thrd_query_state_t fn)
+{
+    query_state_cb = fn;
+}
+
 struct thread_t *thrd_next(void)
 {
     struct thread_t *p_thrd = RNBL_HEAD;
+    uint32_t retval = 0;
+
     /*
      * First runnable thread has highest priority since threads are
      * sorted by priority.
      */
-    while (p_thrd && p_thrd->state != THRD_STATE_RUNNABLE) {
+    while (p_thrd) {
+        /* Change thread state if any signal changed */
+        p_thrd->state = query_state_cb(p_thrd, &retval);
+
+        if (p_thrd->state == THRD_STATE_RET_VAL_AVAIL) {
+            tfm_arch_set_context_ret_code(p_thrd->p_context_ctrl, retval);
+            p_thrd->state = THRD_STATE_RUNNABLE;
+        }
+
+        if (p_thrd->state == THRD_STATE_RUNNABLE) {
+            break;
+        }
+
         p_thrd = p_thrd->next;
     }
 
diff --git a/secure_fw/spm/cmsis_psa/thread.h b/secure_fw/spm/cmsis_psa/thread.h
index ab279fc..2250a31 100644
--- a/secure_fw/spm/cmsis_psa/thread.h
+++ b/secure_fw/spm/cmsis_psa/thread.h
@@ -17,6 +17,7 @@
 #define THRD_STATE_BLOCK          2
 #define THRD_STATE_DETACH         3
 #define THRD_STATE_INVALID        4
+#define THRD_STATE_RET_VAL_AVAIL  5
 
 /* Priorities. Lower value has higher priority */
 #define THRD_PRIOR_HIGHEST        0x0
@@ -44,6 +45,9 @@
     struct thread_t *next;              /* Next thread in list               */
 };
 
+/* Query thread state function type */
+typedef uint32_t (*thrd_query_state_t)(struct thread_t *p_thrd,
+                                       uint32_t *p_retval);
 /*
  * Definition for the current thread and its access helper preprocessor.
  * The definition needs to be declared in one of the sources.
@@ -97,6 +101,14 @@
 #define THRD_EXPECTING_SCHEDULE() (!(thrd_next() == CURRENT_THREAD))
 
 /*
+ * Init the global query state callback function pointer.
+ *
+ * Parameters :
+ *  fn             -     Query state function pointer.
+ */
+void thrd_set_query_callback(thrd_query_state_t fn);
+
+/*
  * Set thread state, and updates the runnable head.
  *
  * Parameters :
diff --git a/secure_fw/spm/ffm/backend_ipc.c b/secure_fw/spm/ffm/backend_ipc.c
index b712b5c..e0b7f05 100644
--- a/secure_fw/spm/ffm/backend_ipc.c
+++ b/secure_fw/spm/ffm/backend_ipc.c
@@ -52,6 +52,55 @@
 
 extern uint32_t scheduler_lock;
 
+/*
+ * Query the state of current thread.
+ */
+static uint32_t query_state(struct thread_t *p_thrd, uint32_t *p_retval)
+{
+    struct critical_section_t cs_signal = CRITICAL_SECTION_STATIC_INIT;
+    struct partition_t *p_pt = NULL;
+    uint32_t state = p_thrd->state;
+    psa_signal_t signal_ret = 0;
+
+    /* Get current partition of thread. */
+    p_pt = TO_CONTAINER(p_thrd->p_context_ctrl,
+                        struct partition_t, ctx_ctrl);
+
+    CRITICAL_SECTION_ENTER(cs_signal);
+
+    signal_ret = p_pt->signals_waiting & p_pt->signals_asserted;
+
+    if (signal_ret) {
+        /*
+         * If the partition is waiting some signals and any of them is asserted,
+         * change thread to be THRD_STATE_RET_VAL_AVAIL and fill the retval. If
+         * the waiting signal is TFM_IPC_REPLY_SIGNAL, it means the Secure
+         * Partition is waiting for the services to be fulfilled, then the
+         * return value comes from the backend_replying() by the server
+         * Partition. For other waiting signals by psa_wait(), the return value
+         * is just the signal.
+         */
+        if (signal_ret == TFM_IPC_REPLY_SIGNAL) {
+            p_pt->signals_asserted &= ~TFM_IPC_REPLY_SIGNAL;
+            *p_retval = (uint32_t)p_pt->reply_value;
+        } else {
+            *p_retval = signal_ret;
+        }
+
+        p_pt->signals_waiting = 0;
+        state = THRD_STATE_RET_VAL_AVAIL;
+    } else if (p_pt->signals_waiting != 0) {
+        /*
+         * If the thread is waiting some signals but none of them is asserted,
+         * block the thread.
+         */
+        state = THRD_STATE_BLOCK;
+    }
+
+    CRITICAL_SECTION_LEAVE(cs_signal);
+    return state;
+}
+
 static void prv_process_metadata(struct partition_t *p_pt)
 {
     const struct partition_load_info_t *p_pt_ldi;
@@ -102,7 +151,6 @@
 {
     struct partition_t *p_owner = NULL;
     psa_signal_t signal = 0;
-    struct critical_section_t cs_assert = CRITICAL_SECTION_STATIC_INIT;
 
     if (!handle || !service || !service->p_ldinf || !service->partition) {
         return PSA_ERROR_PROGRAMMER_ERROR;
@@ -111,21 +159,10 @@
     p_owner = service->partition;
     signal = service->p_ldinf->signal;
 
-    CRITICAL_SECTION_ENTER(cs_assert);
-
     UNI_LIST_INSERT_AFTER(p_owner, handle, p_handles);
 
     /* Messages put. Update signals */
-    p_owner->signals_asserted |= signal;
-
-    if (p_owner->signals_waiting & signal) {
-        if (p_owner->thrd.state == THRD_STATE_BLOCK) {
-            thrd_set_state(&p_owner->thrd, THRD_STATE_RUNNABLE);
-            tfm_arch_set_context_ret_code(p_owner->thrd.p_context_ctrl,
-                        (p_owner->signals_asserted & p_owner->signals_waiting));
-        }
-        p_owner->signals_waiting &= ~signal;
-    }
+    backend_assert_signal(p_owner, signal);
 
     /*
      * If it is a NS request via RPC, it is unnecessary to block current
@@ -133,10 +170,8 @@
      */
 
     if (!is_tfm_rpc_msg(handle)) {
-        thrd_set_state(&handle->p_client->thrd, THRD_STATE_BLOCK);
-        handle->p_client->signals_asserted |= TFM_IPC_REPLY_SIGNAL;
+        backend_wait_signals(handle->p_client, TFM_IPC_REPLY_SIGNAL);
     }
-    CRITICAL_SECTION_LEAVE(cs_assert);
 
     handle->status = TFM_HANDLE_STATUS_ACTIVE;
 
@@ -145,21 +180,12 @@
 
 psa_status_t backend_replying(struct conn_handle_t *handle, int32_t status)
 {
-    struct critical_section_t cs_assert = CRITICAL_SECTION_STATIC_INIT;
-
-    CRITICAL_SECTION_ENTER(cs_assert);
-
     if (is_tfm_rpc_msg(handle)) {
         tfm_rpc_client_call_reply(handle, status);
     } else {
-        if (handle->p_client->signals_asserted & TFM_IPC_REPLY_SIGNAL) {
-            thrd_set_state(&handle->p_client->thrd, THRD_STATE_RUNNABLE);
-            tfm_arch_set_context_ret_code(handle->p_client->thrd.p_context_ctrl,
-                                        status);
-            handle->p_client->signals_asserted &= ~TFM_IPC_REPLY_SIGNAL;
-        }
+        handle->p_client->reply_value = (uintptr_t)status;
+        backend_assert_signal(handle->p_client, TFM_IPC_REPLY_SIGNAL);
     }
-    CRITICAL_SECTION_LEAVE(cs_assert);
 
     /*
      * 'psa_reply' exists in IPC model only and returns 'void'. Return
@@ -220,6 +246,9 @@
     SPM_ASSERT(SPM_THREAD_CONTEXT);
 #endif
 
+    /* Init thread callback function. */
+    thrd_set_query_callback(query_state);
+
     partition_meta_indicator_pos = (uintptr_t *)hal_mem_sp_meta_start;
     control = thrd_start_scheduler(&CURRENT_THREAD);
 
@@ -234,38 +263,40 @@
     return control;
 }
 
-psa_signal_t backend_wait(struct partition_t *p_pt, psa_signal_t signal_mask)
+psa_signal_t backend_wait_signals(struct partition_t *p_pt, psa_signal_t signals)
 {
-    struct critical_section_t cs_assert = CRITICAL_SECTION_STATIC_INIT;
+    struct critical_section_t cs_signal = CRITICAL_SECTION_STATIC_INIT;
     psa_signal_t ret_signal;
 
-    /*
-     * 'backend_wait()' sets the waiting signal mask for partition, and
-     * blocks the partition thread state to wait for signals.
-     * These changes should be inside the ciritical section to avoid
-     * 'signal_waiting' or the thread state to be changed by interrupts
-     * while this function is reading or writing values.
-     */
-    CRITICAL_SECTION_ENTER(cs_assert);
-
-    ret_signal = p_pt->signals_asserted & signal_mask;
-    if (ret_signal == 0) {
-        p_pt->signals_waiting = signal_mask;
-        thrd_set_state(&p_pt->thrd, THRD_STATE_BLOCK);
+    if (!p_pt) {
+        tfm_core_panic();
     }
-    CRITICAL_SECTION_LEAVE(cs_assert);
+
+    CRITICAL_SECTION_ENTER(cs_signal);
+
+    ret_signal = p_pt->signals_asserted & signals;
+    if (ret_signal == 0) {
+        p_pt->signals_waiting = signals;
+    }
+
+    CRITICAL_SECTION_LEAVE(cs_signal);
 
     return ret_signal;
 }
 
-void backend_wake_up(struct partition_t *p_pt)
+uint32_t backend_assert_signal(struct partition_t *p_pt, psa_signal_t signal)
 {
-    if (p_pt->thrd.state == THRD_STATE_BLOCK) {
-        thrd_set_state(&p_pt->thrd, THRD_STATE_RUNNABLE);
-        tfm_arch_set_context_ret_code(p_pt->thrd.p_context_ctrl,
-                            (p_pt->signals_asserted & p_pt->signals_waiting));
+    struct critical_section_t cs_signal = CRITICAL_SECTION_STATIC_INIT;
+
+    if (!p_pt) {
+        tfm_core_panic();
     }
-    p_pt->signals_waiting = 0;
+
+    CRITICAL_SECTION_ENTER(cs_signal);
+    p_pt->signals_asserted |= signal;
+    CRITICAL_SECTION_LEAVE(cs_signal);
+
+    return PSA_SUCCESS;
 }
 
 uint64_t ipc_schedule(void)
diff --git a/secure_fw/spm/ffm/backend_sfn.c b/secure_fw/spm/ffm/backend_sfn.c
index bdc74d3..198b060 100644
--- a/secure_fw/spm/ffm/backend_sfn.c
+++ b/secure_fw/spm/ffm/backend_sfn.c
@@ -149,15 +149,17 @@
     return EXC_RETURN_THREAD_PSP;
 }
 
-psa_signal_t backend_wait(struct partition_t *p_pt, psa_signal_t signal_mask)
+psa_signal_t backend_wait_signals(struct partition_t *p_pt, psa_signal_t signals)
 {
-    while (!(p_pt->signals_asserted & signal_mask))
+    while (!(p_pt->signals_asserted & signals))
         ;
 
-    return p_pt->signals_asserted & signal_mask;
+    return p_pt->signals_asserted & signals;
 }
 
-void backend_wake_up(struct partition_t *p_pt)
+uint32_t backend_assert_signal(struct partition_t *p_pt, psa_signal_t signal)
 {
-    (void)p_pt;
+    p_pt->signals_asserted |= signal;
+
+    return PSA_SUCCESS;
 }
diff --git a/secure_fw/spm/ffm/interrupt.c b/secure_fw/spm/ffm/interrupt.c
index 1f28123..7ec611e 100644
--- a/secure_fw/spm/ffm/interrupt.c
+++ b/secure_fw/spm/ffm/interrupt.c
@@ -20,6 +20,7 @@
 #include "utilities.h"
 
 #include "load/spm_load_api.h"
+#include "ffm/backend.h"
 
 extern uintptr_t spm_boundary;
 
@@ -174,7 +175,7 @@
     }
 
     if (flih_result == PSA_FLIH_SIGNAL) {
-        spm_assert_signal(p_pt, p_ildi->signal);
+        backend_assert_signal(p_pt, p_ildi->signal);
         /* In SFN backend, there is only one thread, no thread switch. */
 #if CONFIG_TFM_SPM_BACKEND_SFN != 1
         if (THRD_EXPECTING_SCHEDULE()) {
diff --git a/secure_fw/spm/ffm/psa_api.c b/secure_fw/spm/ffm/psa_api.c
index f72609b..adb8d70 100644
--- a/secure_fw/spm/ffm/psa_api.c
+++ b/secure_fw/spm/ffm/psa_api.c
@@ -136,17 +136,14 @@
     }
 
     /*
-     * backend_wake_up() blocks the caller thread if no signals are
-     * available. In this case, the return value of this function is temporary
-     * set into runtime context. After new signal(s) are available, the return
-     * value is updated with the available signal(s) and blocked thread gets
-     * to run.
+     * After new signal(s) are available, the return value will be updated in
+     * PendSV and blocked thread gets to run.
      */
     if (timeout == PSA_BLOCK) {
-        return backend_wait(partition, signal_mask);
+        return backend_wait_signals(partition, signal_mask);
+    } else {
+        return partition->signals_asserted & signal_mask;
     }
-
-    return partition->signals_asserted & signal_mask;
 }
 #endif
 
@@ -537,7 +534,7 @@
 {
     struct partition_t *p_pt = tfm_spm_get_partition_by_id(partition_id);
 
-    spm_assert_signal(p_pt, PSA_DOORBELL);
+    backend_assert_signal(p_pt, PSA_DOORBELL);
 }
 
 void tfm_spm_partition_psa_clear(void)
diff --git a/secure_fw/spm/include/ffm/backend.h b/secure_fw/spm/include/ffm/backend.h
index 9d167c1..57bdc22 100644
--- a/secure_fw/spm/include/ffm/backend.h
+++ b/secure_fw/spm/include/ffm/backend.h
@@ -52,18 +52,15 @@
  */
 psa_status_t backend_replying(struct conn_handle_t *handle, int32_t status);
 
-/*
- * Runtime model-specific Partition wait operation.
- * Put the Partition to a status that waits for signals.
+/**
+ * \brief Set the wait signal pattern in current partition.
  */
-psa_signal_t backend_wait(struct partition_t *p_pt, psa_signal_t signal_mask);
+psa_signal_t backend_wait_signals(struct partition_t *p_pt, psa_signal_t signals);
 
-/*
- * Runtime model-specific Partition wake up operation.
- * Wakes up the Partition with the asserted signals in 'p_pt'.
+/**
+ * \brief Set the asserted signal pattern in current partition.
  */
-void backend_wake_up(struct partition_t *p_pt);
-
+uint32_t backend_assert_signal(struct partition_t *p_pt, psa_signal_t signal);
 
 /* The component list, and a MACRO indicate this is not a common global. */
 extern struct partition_head_t partition_listhead;