SPM: Implement MM-IOVEC Secure Partition API

- Add APIs for mapping and unmapping Secure Partition RoT Service input
  and output vectors.
- Add changes to existing Secure Partition APIs when enable MM-IOVEC.
  They are psa_read(), psa_skip() and psa_write().

Note: MM-IOVEC can only support isolation level 1 currently.

Change-Id: I424181025622cb2520bc7a3ab41e305ea722ace3
Signed-off-by: Shawn Shan <Shawn.Shan@arm.com>
diff --git a/secure_fw/spm/ffm/psa_api.c b/secure_fw/spm/ffm/psa_api.c
index 0190c02..af35a2d 100644
--- a/secure_fw/spm/ffm/psa_api.c
+++ b/secure_fw/spm/ffm/psa_api.c
@@ -29,6 +29,62 @@
 #define GET_STATELESS_SERVICE(index)    (stateless_services_ref_tbl[index])
 extern struct service_t *stateless_services_ref_tbl[];
 
+#if PSA_FRAMEWORK_HAS_MM_IOVEC
+
+/*
+ * The MM-IOVEC status
+ * The max total number of invec and outvec is 8.
+ * Each invec/outvec takes 4 bit, 32 bits in total.
+ *
+ * The encoding format of the MM-IOVEC status:
+ *--------------------------------------------------------------
+ *|  Bit   |  31 - 28  |  27 - 24  | ... |  7 - 4   |  3 - 0   |
+ *--------------------------------------------------------------
+ *| Vector | outvec[3] | outvec[2] | ... | invec[1] | invec[0] |
+ *--------------------------------------------------------------
+ *
+ * Take invec[0] as an example:
+ *
+ * bit 0:  whether invec[0] has been mapped.
+ * bit 1:  whether invec[0] has been unmapped.
+ * bit 2:  whether invec[0] has been accessed using psa_read(), psa_skip() or
+ *         psa_write().
+ * bit 3:  reserved for invec[0].
+ */
+
+#define IOVEC_STATUS_BITS              4   /* Each vector occupies 4 bits. */
+#define OUTVEC_IDX_BASE                4   /*
+                                            * Base index of outvec.
+                                            * There are four invecs in front of
+                                            * outvec.
+                                            */
+#define INVEC_IDX_BASE                 0   /* Base index of invec. */
+
+#define IOVEC_MAPPED_BIT               (1U << 0)
+#define IOVEC_UNMAPPED_BIT             (1U << 1)
+#define IOVEC_ACCESSED_BIT             (1U << 2)
+
+#define IOVEC_IS_MAPPED(msg, iovec_idx)      \
+    ((((msg)->iovec_status) >> ((iovec_idx) * IOVEC_STATUS_BITS)) & \
+                               IOVEC_MAPPED_BIT)
+#define IOVEC_IS_UNMAPPED(msg, iovec_idx)    \
+    ((((msg)->iovec_status) >> ((iovec_idx) * IOVEC_STATUS_BITS)) & \
+                               IOVEC_UNMAPPED_BIT)
+#define IOVEC_IS_ACCESSED(msg, iovec_idx)    \
+    ((((msg)->iovec_status) >> ((iovec_idx) * IOVEC_STATUS_BITS)) & \
+                               IOVEC_ACCESSED_BIT)
+#define SET_IOVEC_MAPPED(msg, iovec_idx)     \
+    (((msg)->iovec_status) |= (IOVEC_MAPPED_BIT <<   \
+                              ((iovec_idx) * IOVEC_STATUS_BITS)))
+#define SET_IOVEC_UNMAPPED(msg, iovec_idx)   \
+    (((msg)->iovec_status) |= (IOVEC_UNMAPPED_BIT << \
+                              ((iovec_idx) * IOVEC_STATUS_BITS)))
+#define SET_IOVEC_ACCESSED(msg, iovec_idx)   \
+    (((msg)->iovec_status) |= (IOVEC_ACCESSED_BIT << \
+                              ((iovec_idx) * IOVEC_STATUS_BITS)))
+
+#endif /* PSA_FRAMEWORK_HAS_MM_IOVEC */
+
 uint32_t tfm_spm_get_lifecycle_state(void)
 {
     /*
@@ -546,6 +602,18 @@
         return 0;
     }
 
+#if PSA_FRAMEWORK_HAS_MM_IOVEC
+    /*
+     * It is a fatal error if the input vector has already been mapped using
+     * psa_map_invec().
+     */
+    if (IOVEC_IS_MAPPED(msg, (invec_idx + INVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    SET_IOVEC_ACCESSED(msg, (invec_idx + INVEC_IDX_BASE));
+#endif
+
     /*
      * Copy the client data to the service buffer. It is a fatal error
      * if the memory reference for buffer is invalid or not read-write.
@@ -599,6 +667,18 @@
         return 0;
     }
 
+#if PSA_FRAMEWORK_HAS_MM_IOVEC
+    /*
+     * It is a fatal error if the input vector has already been mapped using
+     * psa_map_invec().
+     */
+    if (IOVEC_IS_MAPPED(msg, (invec_idx + INVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    SET_IOVEC_ACCESSED(msg, (invec_idx + INVEC_IDX_BASE));
+#endif
+
     /*
      * If num_bytes is greater than the remaining size of the input vector then
      * the remaining size of the input vector is used.
@@ -657,6 +737,18 @@
         tfm_core_panic();
     }
 
+#if PSA_FRAMEWORK_HAS_MM_IOVEC
+    /*
+     * It is a fatal error if the output vector has already been mapped using
+     * psa_map_outvec().
+     */
+    if (IOVEC_IS_MAPPED(msg, (outvec_idx + OUTVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    SET_IOVEC_ACCESSED(msg, (outvec_idx + OUTVEC_IDX_BASE));
+#endif
+
     /*
      * Copy the service buffer to client outvecs. It is a fatal error
      * if the memory reference for buffer is invalid or not readable.
@@ -732,6 +824,29 @@
         break;
     default:
         if (msg->msg.type >= PSA_IPC_CALL) {
+
+#if PSA_FRAMEWORK_HAS_MM_IOVEC
+
+            /*
+             * If the unmapped function is not called for an input/output vector
+             * that has been mapped, the framework will remove the mapping.
+             */
+            int i;
+
+            for (i = 0; i < PSA_MAX_IOVEC * 2; i++) {
+                if (IOVEC_IS_MAPPED(msg, i) && (!IOVEC_IS_UNMAPPED(msg, i))) {
+                    SET_IOVEC_UNMAPPED(msg, i);
+                    /*
+                     * Any output vectors that are still mapped will report that
+                     * zero bytes have been written.
+                     */
+                    if (i >= OUTVEC_IDX_BASE) {
+                        msg->outvec[i - OUTVEC_IDX_BASE].len = 0;
+                    }
+                }
+            }
+
+#endif
             /* Reply to a request message. Return values are based on status */
             ret = status;
             /*
@@ -908,3 +1023,276 @@
     partition->signals_asserted &= ~irq_signal;
     CRITICAL_SECTION_LEAVE(cs_assert);
 }
+
+#if PSA_FRAMEWORK_HAS_MM_IOVEC
+
+const void *tfm_spm_partition_psa_map_invec(psa_handle_t msg_handle,
+                                            uint32_t invec_idx)
+{
+    struct tfm_msg_body_t *msg = NULL;
+    uint32_t privileged;
+    struct partition_t *partition = NULL;
+
+    /* It is a fatal error if message handle is invalid */
+    msg = tfm_spm_get_msg_from_handle(msg_handle);
+    if (!msg) {
+        tfm_core_panic();
+    }
+
+    partition = msg->service->partition;
+    privileged = tfm_spm_partition_get_privileged_mode(
+                                                     partition->p_ldinf->flags);
+
+    /*
+     * It is a fatal error if MM-IOVEC has not been enabled for the RoT
+     * Service that received the message.
+     */
+    if (!SERVICE_ENABLED_MM_IOVEC(msg->service->p_ldinf->flags)) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if message handle does not refer to a request
+     * message.
+     */
+    if (msg->msg.type < PSA_IPC_CALL) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if invec_idx is equal to or greater than
+     * PSA_MAX_IOVEC.
+     */
+    if (invec_idx >= PSA_MAX_IOVEC) {
+        tfm_core_panic();
+    }
+
+    /* It is a fatal error if the input vector has length zero. */
+    if (msg->msg.in_size[invec_idx] == 0) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the input vector has already been mapped using
+     * psa_map_invec().
+     */
+    if (IOVEC_IS_MAPPED(msg, (invec_idx + INVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the input vector has already been accessed
+     * using psa_read() or psa_skip().
+     */
+    if (IOVEC_IS_ACCESSED(msg, (invec_idx + INVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the memory reference for the wrap input vector is
+     * invalid or not readable.
+     */
+    if (tfm_memory_check(msg->invec[invec_idx].base, msg->invec[invec_idx].len,
+        false, TFM_MEMORY_ACCESS_RO, privileged) != SPM_SUCCESS) {
+        tfm_core_panic();
+    }
+
+    SET_IOVEC_MAPPED(msg, (invec_idx + INVEC_IDX_BASE));
+
+    return msg->invec[invec_idx].base;
+}
+
+void tfm_spm_partition_psa_unmap_invec(psa_handle_t msg_handle,
+                                       uint32_t invec_idx)
+{
+    struct tfm_msg_body_t *msg = NULL;
+
+    /* It is a fatal error if message handle is invalid */
+    msg = tfm_spm_get_msg_from_handle(msg_handle);
+    if (!msg) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if MM-IOVEC has not been enabled for the RoT
+     * Service that received the message.
+     */
+    if (!SERVICE_ENABLED_MM_IOVEC(msg->service->p_ldinf->flags)) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if message handle does not refer to a request
+     * message.
+     */
+    if (msg->msg.type < PSA_IPC_CALL) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if invec_idx is equal to or greater than
+     * PSA_MAX_IOVEC.
+     */
+    if (invec_idx >= PSA_MAX_IOVEC) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if The input vector has not been mapped by a call to
+     * psa_map_invec().
+     */
+    if (!IOVEC_IS_MAPPED(msg, (invec_idx + INVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the input vector has already been unmapped by a
+     * call to psa_unmap_invec().
+     */
+    if (IOVEC_IS_UNMAPPED(msg, (invec_idx + INVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    SET_IOVEC_UNMAPPED(msg, (invec_idx + INVEC_IDX_BASE));
+}
+
+void *tfm_spm_partition_psa_map_outvec(psa_handle_t msg_handle,
+                                       uint32_t outvec_idx)
+{
+    struct tfm_msg_body_t *msg = NULL;
+    uint32_t privileged;
+    struct partition_t *partition = NULL;
+
+    /* It is a fatal error if message handle is invalid */
+    msg = tfm_spm_get_msg_from_handle(msg_handle);
+    if (!msg) {
+        tfm_core_panic();
+    }
+
+    partition = msg->service->partition;
+    privileged = tfm_spm_partition_get_privileged_mode(
+                                                     partition->p_ldinf->flags);
+
+    /*
+     * It is a fatal error if MM-IOVEC has not been enabled for the RoT
+     * Service that received the message.
+     */
+    if (!SERVICE_ENABLED_MM_IOVEC(msg->service->p_ldinf->flags)) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if message handle does not refer to a request
+     * message.
+     */
+    if (msg->msg.type < PSA_IPC_CALL) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if outvec_idx is equal to or greater than
+     * PSA_MAX_IOVEC.
+     */
+    if (outvec_idx >= PSA_MAX_IOVEC) {
+        tfm_core_panic();
+    }
+
+    /* It is a fatal error if the output vector has length zero. */
+    if (msg->msg.out_size[outvec_idx] == 0) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the output vector has already been mapped using
+     * psa_map_outvec().
+     */
+    if (IOVEC_IS_MAPPED(msg, (outvec_idx + OUTVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the output vector has already been accessed
+     * using psa_write().
+     */
+    if (IOVEC_IS_ACCESSED(msg, (outvec_idx + OUTVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the output vector is invalid or not read-write.
+     */
+    if (tfm_memory_check(msg->outvec[outvec_idx].base,
+                         msg->outvec[outvec_idx].len, false,
+                         TFM_MEMORY_ACCESS_RW, privileged) != SPM_SUCCESS) {
+        tfm_core_panic();
+    }
+    SET_IOVEC_MAPPED(msg, (outvec_idx + OUTVEC_IDX_BASE));
+
+    return msg->outvec[outvec_idx].base;
+}
+
+void tfm_spm_partition_psa_unmap_outvec(psa_handle_t msg_handle,
+                                        uint32_t outvec_idx, size_t len)
+{
+    struct tfm_msg_body_t *msg = NULL;
+
+    /* It is a fatal error if message handle is invalid */
+    msg = tfm_spm_get_msg_from_handle(msg_handle);
+    if (!msg) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if MM-IOVEC has not been enabled for the RoT
+     * Service that received the message.
+     */
+    if (!SERVICE_ENABLED_MM_IOVEC(msg->service->p_ldinf->flags)) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if message handle does not refer to a request
+     * message.
+     */
+    if (msg->msg.type < PSA_IPC_CALL) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if outvec_idx is equal to or greater than
+     * PSA_MAX_IOVEC.
+     */
+    if (outvec_idx >= PSA_MAX_IOVEC) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if len is greater than the output vector size.
+     */
+    if (len > msg->msg.out_size[outvec_idx]) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if The output vector has not been mapped by a call to
+     * psa_map_outvec().
+     */
+    if (!IOVEC_IS_MAPPED(msg, (outvec_idx + OUTVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    /*
+     * It is a fatal error if the output vector has already been unmapped by a
+     * call to psa_unmap_outvec().
+     */
+    if (IOVEC_IS_UNMAPPED(msg, (outvec_idx + OUTVEC_IDX_BASE))) {
+        tfm_core_panic();
+    }
+
+    SET_IOVEC_UNMAPPED(msg, (outvec_idx + OUTVEC_IDX_BASE));
+
+    /* Update the write number */
+    msg->outvec[outvec_idx].len = len;
+}
+
+#endif /* PSA_FRAMEWORK_HAS_MM_IOVEC */