Crypto: Add IPC compatibility

This patch introduces compatibility in the Crypto
service with the IPC infrastructure of TF-M.

Change-Id: I5a095780e1f2bd489c83cfbca138ca6dd0bfe9ba
Signed-off-by: Antonio de Angelis <antonio.deangelis@arm.com>
diff --git a/secure_fw/services/crypto/crypto_init.c b/secure_fw/services/crypto/crypto_init.c
index 1ec7dee..aaabea8 100644
--- a/secure_fw/services/crypto/crypto_init.c
+++ b/secure_fw/services/crypto/crypto_init.c
@@ -8,6 +8,254 @@
 #include "tfm_crypto_api.h"
 #include "crypto_engine.h"
 
+#ifdef TFM_PSA_API
+#include "psa_service.h"
+#include "tfm_crypto_signal.h"
+#include "secure_fw/core/tfm_memory_utils.h"
+
+/**
+ * \brief Table containing all the Uniform Signature API exposed
+ *        by the TF-M Crypto partition
+ */
+static const tfm_crypto_us_t sfid_func_table[TFM_CRYPTO_SFID_MAX] = {
+    tfm_crypto_import_key,
+    tfm_crypto_destroy_key,
+    tfm_crypto_get_key_information,
+    tfm_crypto_export_key,
+    tfm_crypto_key_policy_init,
+    tfm_crypto_key_policy_set_usage,
+    tfm_crypto_key_policy_get_usage,
+    tfm_crypto_key_policy_get_algorithm,
+    tfm_crypto_set_key_policy,
+    tfm_crypto_get_key_policy,
+    tfm_crypto_set_key_lifetime,
+    tfm_crypto_get_key_lifetime,
+    tfm_crypto_cipher_set_iv,
+    tfm_crypto_cipher_encrypt_setup,
+    tfm_crypto_cipher_decrypt_setup,
+    tfm_crypto_cipher_update,
+    tfm_crypto_cipher_abort,
+    tfm_crypto_cipher_finish,
+    tfm_crypto_hash_setup,
+    tfm_crypto_hash_update,
+    tfm_crypto_hash_finish,
+    tfm_crypto_hash_verify,
+    tfm_crypto_hash_abort,
+    tfm_crypto_mac_sign_setup,
+    tfm_crypto_mac_verify_setup,
+    tfm_crypto_mac_update,
+    tfm_crypto_mac_sign_finish,
+    tfm_crypto_mac_verify_finish,
+    tfm_crypto_mac_abort,
+    tfm_crypto_aead_encrypt,
+    tfm_crypto_aead_decrypt
+};
+
+/**
+ * \brief Aligns a value x up to an alignment a.
+ */
+#define ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1))
+
+/**
+ * \brief Maximum alignment required by any iovec parameters to the TF-M Crypto
+ *        partition.
+ */
+#define TFM_CRYPTO_IOVEC_ALIGNMENT (4u)
+
+/**
+ * \brief Default size of the internal scratch buffer used for IOVec allocations
+ *        in bytes
+ */
+#ifndef TFM_CRYPTO_IOVEC_BUFFER_SIZE
+#define TFM_CRYPTO_IOVEC_BUFFER_SIZE (1024)
+#endif
+
+/**
+ * \brief Internal scratch used for IOVec allocations
+ *
+ */
+static struct tfm_crypto_scratch {
+    __attribute__((__aligned__(TFM_CRYPTO_IOVEC_ALIGNMENT)))
+    uint8_t buf[TFM_CRYPTO_IOVEC_BUFFER_SIZE];
+    uint32_t alloc_index;
+} scratch = {.buf = {0}, .alloc_index = 0};
+
+static psa_status_t tfm_crypto_alloc_scratch(size_t requested_size, void **buf)
+{
+    /* Ensure alloc_index remains aligned to the required iovec alignment */
+    requested_size = ALIGN(requested_size, TFM_CRYPTO_IOVEC_ALIGNMENT);
+
+    if (requested_size > (sizeof(scratch.buf) - scratch.alloc_index)) {
+        return PSA_ERROR_INSUFFICIENT_MEMORY;
+    }
+
+    /* Compute the pointer to the allocated space */
+    *buf = (void *)&scratch.buf[scratch.alloc_index];
+
+    /* Increase the allocated size */
+    scratch.alloc_index += requested_size;
+
+    return PSA_SUCCESS;
+}
+
+static psa_status_t tfm_crypto_clear_scratch(void)
+{
+    scratch.alloc_index = 0;
+    (void)tfm_memset(scratch.buf, 0, sizeof(scratch.buf));
+
+    return PSA_SUCCESS;
+}
+
+static psa_status_t tfm_crypto_call_sfn(psa_msg_t *msg,
+                                        struct tfm_crypto_pack_iovec *iov,
+                                        const uint32_t sfn_id)
+{
+    psa_status_t status = PSA_SUCCESS;
+    size_t in_len = 0, out_len = 0, i, read_size;
+    psa_invec in_vec[PSA_MAX_IOVEC] = { {0} };
+    psa_outvec out_vec[PSA_MAX_IOVEC] = { {0} };
+    void *alloc_buf_ptr = NULL;
+
+    /* Check the number of in_vec filled */
+    while ((in_len < PSA_MAX_IOVEC) && (msg->in_size[in_len] != 0)) {
+        in_len++;
+    }
+
+    /* There will always be a tfm_crypto_pack_iovec in the first iovec */
+    if (in_len < 1) {
+        return PSA_ERROR_UNKNOWN_ERROR;
+    }
+    /* Initialise the first iovec with the IOV read when parsing */
+    in_vec[0].base = iov;
+    in_vec[0].len = sizeof(struct tfm_crypto_pack_iovec);
+
+    /* Alloc/read from the second element as the first is read when parsing */
+    for (i = 1; i < in_len; i++) {
+        /* Allocate necessary space in the internal scratch */
+        status = tfm_crypto_alloc_scratch(msg->in_size[i], &alloc_buf_ptr);
+        if (status != PSA_SUCCESS) {
+            return status;
+        }
+        /* Read from the IPC framework inputs into the scratch */
+        read_size = psa_read(msg->handle, i, alloc_buf_ptr, msg->in_size[i]);
+        /* Populate the fields of the input to the secure function */
+        in_vec[i].base = alloc_buf_ptr;
+        in_vec[i].len = msg->in_size[i];
+    }
+
+    /* Check the number of out_vec filled */
+    while ((out_len < PSA_MAX_IOVEC) && (msg->out_size[out_len] != 0)) {
+        out_len++;
+    }
+
+    for (i = 0; i < out_len; i++) {
+        /* Allocate necessary space for the output in the internal scratch */
+        status = tfm_crypto_alloc_scratch(msg->out_size[i], &alloc_buf_ptr);
+        if (status != PSA_SUCCESS) {
+            return status;
+        }
+        /* Populate the fields of the output to the secure function */
+        out_vec[i].base = alloc_buf_ptr;
+        out_vec[i].len = msg->out_size[i];
+    }
+
+    /* Call the uniform signature API */
+    status = sfid_func_table[sfn_id](in_vec, in_len, out_vec, out_len);
+
+    /* Write into the IPC framework outputs from the scratch */
+    for (i = 0; i < out_len; i++) {
+        psa_write(msg->handle, i, out_vec[i].base, out_vec[i].len);
+    }
+
+    /* Clear the allocated internal scratch before returning */
+    if (tfm_crypto_clear_scratch() != PSA_SUCCESS) {
+        return PSA_ERROR_UNKNOWN_ERROR;
+    }
+
+    return status;
+}
+
+static psa_status_t tfm_crypto_parse_msg(psa_msg_t *msg,
+                                         struct tfm_crypto_pack_iovec *iov,
+                                         uint32_t *sfn_id_p)
+{
+    size_t read_size;
+
+    /* Read the in_vec[0] which holds the IOVEC always */
+    read_size = psa_read(msg->handle,
+                         0,
+                         iov,
+                         sizeof(struct tfm_crypto_pack_iovec));
+
+    if (read_size != sizeof(struct tfm_crypto_pack_iovec)) {
+        return PSA_ERROR_UNKNOWN_ERROR;
+    }
+
+    if (iov->sfn_id >= TFM_CRYPTO_SFID_MAX) {
+        *sfn_id_p = TFM_CRYPTO_SFID_INVALID;
+        return PSA_ERROR_UNKNOWN_ERROR;
+    }
+
+    *sfn_id_p = iov->sfn_id;
+
+    return PSA_SUCCESS;
+}
+
+static void tfm_crypto_ipc_handler(void)
+{
+    psa_signal_t signals = 0;
+    psa_msg_t msg;
+    psa_status_t status = PSA_SUCCESS;
+    uint32_t sfn_id = TFM_CRYPTO_SFID_INVALID;
+    struct tfm_crypto_pack_iovec iov = {0};
+
+    while (1) {
+        signals = psa_wait(PSA_WAIT_ANY, PSA_BLOCK);
+        if (signals & TFM_CRYPTO_SIG) {
+            /* Extract the message */
+            if (psa_get(TFM_CRYPTO_SIG, &msg) != PSA_SUCCESS) {
+                /* FIXME: Should be replaced by TF-M error handling */
+                while (1) {
+                    ;
+                }
+            }
+
+            /* Process the message type */
+            switch (msg.type) {
+            case PSA_IPC_CONNECT:
+            case PSA_IPC_DISCONNECT:
+                psa_reply(msg.handle, PSA_SUCCESS);
+                break;
+            case PSA_IPC_CALL:
+                /* Parse the message */
+                status = tfm_crypto_parse_msg(&msg, &iov, &sfn_id);
+                /* Call the dispatcher based on the SFID passed as type */
+                if (sfn_id != TFM_CRYPTO_SFID_INVALID) {
+                    status = tfm_crypto_call_sfn(&msg, &iov, sfn_id);
+                } else {
+                    status = PSA_ERROR_UNKNOWN_ERROR;
+                }
+                psa_reply(msg.handle, status);
+                break;
+            default:
+                /* FIXME: Should be replaced by TF-M error handling */
+                while (1) {
+                    ;
+                }
+            }
+        } else {
+            /* FIXME: Should be replaced by TF-M error handling */
+            while (1) {
+               ;
+            }
+        }
+    }
+
+    /* This is unreachable */
+    return;
+}
+#endif /* TFM_PSA_API */
+
 static psa_status_t tfm_crypto_module_init(void)
 {
     psa_status_t status = PSA_SUCCESS;
@@ -35,13 +283,13 @@
     /* Initialise the engine interface module */
     status = tfm_crypto_engine_init();
     if (status != PSA_SUCCESS) {
-        /* FIXME: For the time being, keep returning success even if the engine
-         * is not initialised correctly. This can be used to test corner cases
-         * without triggering any TF-M recovery mechanism during boot-up if it
-         * recognises that a service has not completed booting correctly.
-         */
-        return PSA_SUCCESS;
+        return status;
     }
 
-    return PSA_SUCCESS;
+#ifdef TFM_PSA_API
+    /* Should not return in normal operations */
+    tfm_crypto_ipc_handler();
+#endif
+
+    return status;
 }