aboutsummaryrefslogtreecommitdiffstats
path: root/security/smc/tf_crypto.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/smc/tf_crypto.c')
-rw-r--r--security/smc/tf_crypto.c1278
1 files changed, 1278 insertions, 0 deletions
diff --git a/security/smc/tf_crypto.c b/security/smc/tf_crypto.c
new file mode 100644
index 0000000..7edca0f
--- /dev/null
+++ b/security/smc/tf_crypto.c
@@ -0,0 +1,1278 @@
+/**
+ * Copyright (c) 2011 Trusted Logic S.A.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "tf_defs.h"
+#include "tf_util.h"
+#include "tf_zebra.h"
+#include "tf_crypto.h"
+#include "tf_dma.h"
+
+#define IO_ADDRESS OMAP2_L4_IO_ADDRESS
+
+#define S_SUCCESS 0x00000000
+#define S_ERROR_GENERIC 0xFFFF0000
+#define S_ERROR_ACCESS_DENIED 0xFFFF0001
+#define S_ERROR_BAD_FORMAT 0xFFFF0005
+#define S_ERROR_BAD_PARAMETERS 0xFFFF0006
+#define S_ERROR_OUT_OF_MEMORY 0xFFFF000C
+#define S_ERROR_SHORT_BUFFER 0xFFFF0010
+#define S_ERROR_UNREACHABLE 0xFFFF3013
+#define S_ERROR_SERVICE 0xFFFF1000
+
+#define CKR_OK 0x00000000
+
+#define PUBLIC_CRYPTO_TIMEOUT_CONST 0x000FFFFF
+
+#define RPC_AES1_CODE PUBLIC_CRYPTO_HWA_AES1
+#define RPC_DES_CODE PUBLIC_CRYPTO_HWA_DES
+#define RPC_SHA_CODE PUBLIC_CRYPTO_HWA_SHA
+
+#define RPC_CRYPTO_COMMAND_MASK 0x000003c0
+
+#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR 0x200
+#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_UNLOCK 0x000
+#define RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_LOCK 0x001
+
+#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT 0x240
+#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_AES1 RPC_AES1_CODE
+#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_DES RPC_DES_CODE
+#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_LOCK_SHA RPC_SHA_CODE
+#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_SUSPEND 0x010
+#define RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_UNINSTALL 0x020
+
+#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS 0x280
+#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1 RPC_AES1_CODE
+#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES RPC_DES_CODE
+#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_SHA RPC_SHA_CODE
+#define RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_RESUME 0x010
+
+#define RPC_CLEAR_GLOBAL_KEY_CONTEXT 0x2c0
+#define RPC_CLEAR_GLOBAL_KEY_CONTEXT_CLEARED_AES 0x001
+#define RPC_CLEAR_GLOBAL_KEY_CONTEXT_CLEARED_DES 0x002
+
+#define ENABLE_CLOCK true
+#define DISABLE_CLOCK false
+
+/*---------------------------------------------------------------------------*/
+/*RPC IN/OUT structures for CUS implementation */
+/*---------------------------------------------------------------------------*/
+
+struct rpc_install_shortcut_lock_accelerator_out {
+ u32 shortcut_id;
+ u32 error;
+};
+
+struct rpc_install_shortcut_lock_accelerator_in {
+ u32 device_context_id;
+ u32 client_session;
+ u32 command_id;
+ u32 key_context;
+ /**
+ *The identifier of the HWA accelerator that this shortcut uses!
+ *Possible values are:
+ *- 1 (RPC_AES1_CODE)
+ *- 4 (RPC_DES_CODE)
+ *- 8 (RPC_SHA_CODE)
+ **/
+ u32 hwa_id;
+ /**
+ *This field defines the algorithm, direction, mode, key size.
+ *It contains some of the bits of the corresponding "CTRL" register
+ *of the accelerator.
+ *
+ *More precisely:
+ *For AES1 accelerator, hwa_ctrl contains the following bits:
+ *- CTR (bit 6):
+ * when 1, selects CTR mode.
+ * when 0, selects CBC or ECB mode (according to CBC bit)
+ *- CBC (bit 5)
+ * when 1, selects CBC mode (but only if CTR=0)
+ * when 0, selects EBC mode (but only if CTR=0)
+ *- DIRECTION (bit 2)
+ * 0: decryption
+ * 1: encryption
+ *
+ *For the DES2 accelerator, hwa_ctrl contains the following bits:
+ *- CBC (bit 4): 1 for CBC, 0 for ECB
+ *- DIRECTION (bit 2): 0 for decryption, 1 for encryption
+ *
+ *For the SHA accelerator, hwa_ctrl contains the following bits:
+ *- ALGO (bit 2:1):
+ * 0x0: MD5
+ * 0x1: SHA1
+ * 0x2: SHA-224
+ * 0x3: SHA-256
+ **/
+ u32 hwa_ctrl;
+ union tf_crypto_operation_state operation_state;
+};
+
+struct rpc_lock_hwa_suspend_shortcut_out {
+ union tf_crypto_operation_state operation_state;
+};
+
+struct rpc_lock_hwa_suspend_shortcut_in {
+ u32 shortcut_id;
+};
+
+struct rpc_resume_shortcut_unlock_hwa_in {
+ u32 shortcut_id;
+ u32 aes1_key_context;
+ u32 reserved;
+ u32 des_key_context;
+ union tf_crypto_operation_state operation_state;
+};
+
+/*------------------------------------------------------------------------- */
+/*
+ * tf_get_device_context(struct cus_context *cus)
+ * search in the all the device context (connection_list) if the CUS context
+ * specified by cus exist.
+ *
+ * If it is found, return the device context where the CUS context is.
+ * If is is not found, return NULL.
+ */
+static struct tf_connection *tf_get_device_context(
+ struct cus_context *cus)
+{
+ struct tf_connection *connection = NULL;
+ struct cus_context *cusFromList = NULL;
+ struct tf_device *dev = tf_get_device();
+
+ spin_lock(&(dev->connection_list_lock));
+ list_for_each_entry(connection, &(dev->connection_list),
+ list) {
+ spin_lock(&(connection->shortcut_list_lock));
+ list_for_each_entry(cusFromList,
+ &(connection->shortcut_list), list) {
+ if ((u32)cusFromList == (u32)cus) {
+ spin_unlock(&(connection->
+ shortcut_list_lock));
+ spin_unlock(&(dev->
+ connection_list_lock));
+ return connection;
+ }
+ }
+ spin_unlock(&(connection->
+ shortcut_list_lock));
+ }
+ spin_unlock(&(dev->connection_list_lock));
+
+ /*cus does not exist */
+ return NULL;
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ * Get the shared memory from the memory block handle coming from secure.
+ * Return NULL if it does not exist.
+ */
+static struct tf_shmem_desc *tf_get_shmem_from_block_handle(
+ struct tf_connection *connection, u32 block)
+{
+ struct tf_shmem_desc *shmem_desc = NULL;
+
+ mutex_lock(&(connection->shmem_mutex));
+
+ list_for_each_entry(shmem_desc,
+ &(connection->used_shmem_list), list) {
+ if ((u32) shmem_desc->block_identifier ==
+ (u32) block) {
+ mutex_unlock(&(connection->shmem_mutex));
+ return shmem_desc;
+ }
+ }
+
+ /* block does not exist */
+ mutex_unlock(&(connection->shmem_mutex));
+
+ return NULL;
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ * HWA public lock or unlock one HWA according algo specified by hwa_id
+ */
+void tf_crypto_lock_hwa(u32 hwa_id, bool do_lock)
+{
+ struct semaphore *s = NULL;
+ struct tf_device *dev = tf_get_device();
+
+ dprintk(KERN_INFO "[pid=%d] %s: hwa_id=0x%04X do_lock=%d\n",
+ current->pid, __func__, hwa_id, do_lock);
+
+ switch (hwa_id) {
+ case RPC_AES1_CODE:
+ s = &dev->aes1_sema;
+ break;
+ case RPC_DES_CODE:
+ s = &dev->des_sema;
+ break;
+ default:
+ case RPC_SHA_CODE:
+ s = &dev->sha_sema;
+ break;
+ }
+
+ if (do_lock == LOCK_HWA) {
+ dprintk(KERN_INFO "tf_crypto_lock_hwa: "
+ "Wait for HWAID=0x%04X\n", hwa_id);
+ while (down_trylock(s))
+ cpu_relax();
+ dprintk(KERN_INFO "tf_crypto_lock_hwa: "
+ "Locked on HWAID=0x%04X\n", hwa_id);
+ } else {
+ up(s);
+ dprintk(KERN_INFO "tf_crypto_lock_hwa: "
+ "Released for HWAID=0x%04X\n", hwa_id);
+ }
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ * HWAs public lock or unlock HWA's specified in the HWA H/A/D fields of RPC
+ * command rpc_command
+ */
+static void tf_crypto_lock_hwas(u32 rpc_command, bool do_lock)
+{
+ dprintk(KERN_INFO
+ "tf_crypto_lock_hwas: rpc_command=0x%08x do_lock=%d\n",
+ rpc_command, do_lock);
+
+ /* perform the locks */
+ if (rpc_command & RPC_AES1_CODE)
+ tf_crypto_lock_hwa(RPC_AES1_CODE, do_lock);
+
+ if (rpc_command & RPC_DES_CODE)
+ tf_crypto_lock_hwa(RPC_DES_CODE, do_lock);
+
+ if (rpc_command & RPC_SHA_CODE)
+ tf_crypto_lock_hwa(RPC_SHA_CODE, do_lock);
+}
+
+/*------------------------------------------------------------------------- */
+/**
+ *Initialize the public crypto DMA channels, global HWA semaphores and handles
+ */
+u32 tf_crypto_init(void)
+{
+ struct tf_device *dev = tf_get_device();
+ u32 error = PUBLIC_CRYPTO_OPERATION_SUCCESS;
+
+ /* Initialize HWAs */
+ tf_aes_init();
+ tf_des_init();
+ tf_digest_init();
+
+ /*initialize the HWA semaphores */
+ sema_init(&dev->aes1_sema, 1);
+ sema_init(&dev->des_sema, 1);
+ sema_init(&dev->sha_sema, 1);
+
+ /*initialize the current key handle loaded in the AESn/DES HWA */
+ dev->aes1_key_context = 0;
+ dev->des_key_context = 0;
+ dev->sham1_is_public = false;
+
+ /*initialize the DMA semaphores */
+ mutex_init(&dev->sm.dma_mutex);
+
+ /*allocate DMA buffer */
+ dev->dma_buffer_length = PAGE_SIZE * 16;
+ dev->dma_buffer = dma_alloc_coherent(NULL,
+ dev->dma_buffer_length,
+ &(dev->dma_buffer_phys),
+ GFP_KERNEL);
+ if (dev->dma_buffer == NULL) {
+ printk(KERN_ERR
+ "tf_crypto_init: Out of memory for DMA buffer\n");
+ error = S_ERROR_OUT_OF_MEMORY;
+ }
+
+ return error;
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ *Initialize the device context CUS fields (shortcut semaphore and public CUS
+ *list)
+ */
+void tf_crypto_init_cus(struct tf_connection *connection)
+{
+ /*initialize the CUS list in the given device context */
+ spin_lock_init(&(connection->shortcut_list_lock));
+ INIT_LIST_HEAD(&(connection->shortcut_list));
+}
+
+/*------------------------------------------------------------------------- */
+/**
+ *Terminate the public crypto (including DMA)
+ */
+void tf_crypto_terminate(void)
+{
+ struct tf_device *dev = tf_get_device();
+
+ if (dev->dma_buffer != NULL) {
+ dma_free_coherent(NULL, dev->dma_buffer_length,
+ dev->dma_buffer,
+ dev->dma_buffer_phys);
+ dev->dma_buffer = NULL;
+ }
+
+ tf_digest_exit();
+ tf_des_exit();
+ tf_aes_exit();
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ *Perform a crypto update operation.
+ *THIS FUNCTION IS CALLED FROM THE IOCTL
+ */
+static bool tf_crypto_update(
+ struct cus_context *cus,
+ struct cus_params *params)
+{
+ bool status = true;
+ dprintk(KERN_INFO
+ "tf_crypto_update(%x): "\
+ "HWAID=0x%x, In=%p, Out=%p, Len=%u\n",
+ (uint32_t) cus, cus->hwa_id,
+ params->input_data,
+ params->output_data, params->input_data_length);
+
+ /* Enable the clock and Process Data */
+ switch (cus->hwa_id) {
+ case RPC_AES1_CODE:
+ tf_crypto_enable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG);
+ cus->operation_state.aes.key_is_public = 0;
+ cus->operation_state.aes.CTRL = cus->hwa_ctrl;
+ status = tf_aes_update(
+ &cus->operation_state.aes,
+ params->input_data,
+ params->output_data,
+ params->input_data_length / AES_BLOCK_SIZE);
+ tf_crypto_disable_clock(PUBLIC_CRYPTO_AES1_CLOCK_REG);
+ break;
+
+ case RPC_DES_CODE:
+ tf_crypto_enable_clock(PUBLIC_CRYPTO_DES3DES_CLOCK_REG);
+ status = tf_des_update(
+ cus->hwa_ctrl,
+ &cus->operation_state.des,
+ params->input_data,
+ params->output_data,
+ params->input_data_length / DES_BLOCK_SIZE);
+ tf_crypto_disable_clock(PUBLIC_CRYPTO_DES3DES_CLOCK_REG);
+ break;
+
+ case RPC_SHA_CODE:
+ tf_crypto_enable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG);
+ cus->operation_state.sha.CTRL = cus->hwa_ctrl;
+ status = tf_digest_update(
+ &cus->operation_state.sha,
+ params->input_data,
+ params->input_data_length);
+ tf_crypto_disable_clock(PUBLIC_CRYPTO_SHA2MD5_CLOCK_REG);
+ break;
+
+ default:
+ BUG_ON(1);
+ break;
+ }
+
+ dprintk(KERN_INFO "tf_crypto_update: Done\n");
+ return status;
+}
+
+/*------------------------------------------------------------------------- */
+
+/*
+ *Check if the command must be intercepted by a CUS or not.
+ *THIS FUNCTION IS CALLED FROM THE USER THREAD (ioctl).
+ *
+ *inputs: struct tf_connection *connection : current device context
+ * tf_command_invoke_client_command *command : the command
+ * bool incrementuse_count : specify if the use_count must be incremented
+ *output:
+ * struct cus_context **cus_ctx : the public CUS
+ * if it is shortcuted
+ *return: true or false
+ *
+ */
+static bool tf_crypto_is_shortcuted_command(
+ struct tf_connection *connection,
+ struct tf_command_invoke_client_command *command,
+ struct cus_context **cus_ctx,
+ bool incrementuse_count)
+{
+ struct tf_device *dev = tf_get_device();
+ struct cus_context *cus = NULL;
+ *cus_ctx = NULL;
+
+ dprintk(KERN_INFO "tf_crypto_is_shortcuted_command: "\
+ "connection=0x%08x, command=0x%08x, "\
+ "CltSession=0x%08x, CmdID=0x%08x\n",
+ (uint32_t) connection, (uint32_t) command,
+ (uint32_t) command->client_session,
+ command->client_command_identifier);
+
+ /*take shortcut_list_lock for the device context
+ *in which the message is sent <=> make sure that nobody is
+ *going to change data while processing */
+ spin_lock(&(connection->shortcut_list_lock));
+
+ /*lookup in the list of shortcuts attached to the device context for a
+ *shortcut context that contains the same client_session as the command
+ *and such that command_id is equal to client_command_identifier of the
+ *INVOKE_CLIENT_COMMAND message. If no such shortcut exists, take the
+ *standard path */
+ list_for_each_entry(
+ cus, &(connection->shortcut_list), list) {
+ dprintk(KERN_INFO
+ "tf_crypto_is_shortcuted_command: "\
+ "command_id = 0x%08x client_session = 0x%08x\n",
+ cus->command_id, cus->client_session);
+
+ if ((cus->client_session == command->client_session)
+ &&
+ (cus->command_id == command->
+ client_command_identifier)) {
+ dprintk(KERN_INFO
+ "tf_crypto_is_shortcuted_command: "\
+ "shortcut is identified\n");
+ /*find a CUS : check if is suspended or not */
+ if (cus->suspended) {
+ /*
+ * suspended of the shortcut context is set to
+ * true, it means that the secure world has
+ * suspended the shortcut to perform an update
+ * on its own. In this case, take the standard
+ * path. This should happen very rarely because
+ * the client and the service should generally
+ * communicate to avoid such a collision
+ */
+ dprintk(KERN_INFO "shortcut exists but "\
+ "suspended\n");
+ goto command_not_shortcutable;
+
+ } else {
+ dprintk(KERN_INFO "shortcut exists\n");
+ /*For AES and DES/3DES operations,
+ *provisionally determine if the accelerator
+ *is loaded with the appropriate key before
+ *deciding to enter the accelerator critical
+ *section. In most cases, if some other thread
+ *or the secure world is currently using the
+ *accelerator, the key won't change.
+ *So, if the key doesn't match now, it is
+ *likely not to match later on, so we'd better
+ *not try to enter the critical section in this
+ *case: */
+
+ if (cus->hwa_id == RPC_AES1_CODE &&
+ cus->
+ key_context != dev->
+ aes1_key_context) {
+ /*For AES operations, atomically read
+ *g_hAES1SSecureKeyContext and check if
+ *it is equal to key_context. If not,
+ *take the standard path <=> do not
+ *shortcut */
+ dprintk(KERN_INFO
+ "shortcut exists but AES key "\
+ "not correct\nkey_context="\
+ "0x%08x vs 0x%08x\n",
+ cus->key_context,
+ dev->
+ aes1_key_context);
+ goto command_not_shortcutable;
+
+ } else if (cus->hwa_id == RPC_DES_CODE
+ && cus->key_context !=
+ dev->
+ des_key_context) {
+ /*
+ * For DES/3DES atomically read
+ * des_key_context and check if
+ * it is equal to key_context. If not,
+ * take the standard path <=> do not
+ * shortcut
+ */
+ dprintk(KERN_INFO
+ "shortcut exists but DES key "
+ "not correct "
+ "des_key_context = 0x%08x"
+ " key_context0x%08x\n",
+ (u32)dev->
+ des_key_context,
+ (u32)cus->key_context);
+ goto command_not_shortcutable;
+ } else if (cus->hwa_id == RPC_SHA_CODE
+ && !dev->sham1_is_public) {
+ /*
+ * For digest operations, atomically
+ * read sham1_is_public and check if it
+ * is true. If not, no shortcut.
+ */
+ dprintk(KERN_INFO
+ "shortcut exists but SHAM1 "
+ "is not accessible in public");
+ goto command_not_shortcutable;
+ }
+ }
+
+ dprintk(KERN_INFO "shortcut exists and enable\n");
+
+ /*Shortcut has been found and context fits with
+ *thread => YES! the command can be shortcuted */
+
+ /*
+ *set the pointer on the corresponding session
+ *(eq CUS context)
+ */
+ *cus_ctx = cus;
+
+ /*
+ *increment use_count if required
+ */
+ if (incrementuse_count)
+ cus->use_count++;
+
+ /*
+ *release shortcut_list_lock
+ */
+ spin_unlock(&(connection->
+ shortcut_list_lock));
+ return true;
+ }
+ }
+
+ command_not_shortcutable:
+ /*
+ *release shortcut_list_lock
+ */
+ spin_unlock(&(connection->shortcut_list_lock));
+ *cus_ctx = NULL;
+ return false;
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ * Pre-process the client command (crypto update operation), i.e., parse the
+ * command message (decode buffers, etc.) THIS FUNCTION IS CALLED FROM THE USER
+ * THREAD (ioctl).
+ *
+ * For incorrect messages, an error is returned and the message will be sent to
+ * secure
+ */
+static bool tf_crypto_parse_command_message(struct tf_connection *connection,
+ struct cus_context *cus,
+ struct tf_command_invoke_client_command *command,
+ struct cus_params *params)
+{
+ u32 param_type;
+ u32 input_data_length;
+ u32 output_data_length;
+ u8 *input_data;
+ u8 *output_data;
+ struct tf_shmem_desc *input_shmem = NULL;
+ struct tf_shmem_desc *output_shmem = NULL;
+
+ dprintk(KERN_INFO
+ "tf_crypto_parse_command_message(%p) : Session=0x%x\n",
+ cus, cus->client_session);
+
+ if (command->params[0].temp_memref.size == 0)
+ return false;
+
+ param_type = TF_GET_PARAM_TYPE(command->param_types, 0);
+ switch (param_type) {
+ case TF_PARAM_TYPE_MEMREF_TEMP_INPUT:
+ if (command->params[0].temp_memref.descriptor == 0)
+ return false;
+
+ input_data = (u8 *) command->params[0].temp_memref.
+ descriptor;
+ input_data_length = command->params[0].temp_memref.size;
+
+ break;
+
+ case TF_PARAM_TYPE_MEMREF_INPUT:
+ input_shmem = tf_get_shmem_from_block_handle(connection,
+ command->params[0].memref.block);
+
+ if (input_shmem == NULL)
+ return false;
+ atomic_inc(&input_shmem->ref_count);
+
+ input_data = input_shmem->pBuffer +
+ command->params[0].memref.offset;
+ input_data_length = command->params[0].memref.size;
+
+ break;
+
+ default:
+ return false;
+ }
+
+ if (cus->hwa_id != RPC_SHA_CODE) {
+ if (command->params[1].temp_memref.size == 0)
+ goto err0;
+
+ /* We need an output buffer as well */
+ param_type = TF_GET_PARAM_TYPE(command->param_types, 1);
+ switch (param_type) {
+ case TF_PARAM_TYPE_MEMREF_TEMP_OUTPUT:
+ output_data =
+ (u8 *) command->params[1].temp_memref.
+ descriptor;
+ output_data_length =
+ command->params[1].temp_memref.size;
+
+ break;
+
+ case TF_PARAM_TYPE_MEMREF_OUTPUT:
+ if (command->params[1].temp_memref.descriptor == 0)
+ return false;
+
+ output_shmem = tf_get_shmem_from_block_handle(
+ connection, command->params[1].memref.block);
+ if (output_shmem == NULL)
+ goto err0;
+ atomic_inc(&output_shmem->ref_count);
+
+ output_data = output_shmem->pBuffer +
+ command->params[1].memref.offset;
+ output_data_length = command->params[1].memref.size;
+
+ break;
+
+ default:
+ dprintk(KERN_ERR "tf_crypto_parse_command_message: "
+ "Encrypt/decrypt operations require an output "
+ "buffer\n");
+
+ goto err0;
+ }
+
+ if (output_data_length < input_data_length) {
+ dprintk(KERN_ERR "tf_crypto_parse_command_message: "
+ "Short buffer: output_data_length = %d < "
+ "input_data_length = %d\n",
+ output_data_length, input_data_length);
+ goto err1;
+ }
+ } else {
+ output_data_length = 0;
+ output_data = NULL;
+ }
+
+ /*
+ * Check if input length is compatible with the algorithm of the
+ * shortcut
+ */
+ switch (cus->hwa_id) {
+ case RPC_AES1_CODE:
+ /* Must be multiple of the AES block size */
+ if ((input_data_length % AES_BLOCK_SIZE) != 0) {
+ dprintk(KERN_ERR
+ "tf_crypto_parse_command_message(%p): "\
+ "Input Data Length invalid [%d] for AES\n",
+ cus, input_data_length);
+ goto err1;
+ }
+ break;
+ case RPC_DES_CODE:
+ /* Must be multiple of the DES block size */
+ if ((input_data_length % DES_BLOCK_SIZE) != 0) {
+ dprintk(KERN_ERR
+ "tf_crypto_parse_command_message(%p): "\
+ "Input Data Length invalid [%d] for DES\n",
+ cus, input_data_length);
+ goto err1;
+ }
+ break;
+ default:
+ /* SHA operation: no constraint on data length */
+ break;
+ }
+
+ params->input_data = input_data;
+ params->input_data_length = input_data_length;
+ params->input_shmem = input_shmem;
+ params->output_data = output_data;
+ params->output_data_length = output_data_length;
+ params->output_shmem = output_shmem;
+
+ return true;
+
+err1:
+ if (output_shmem)
+ atomic_dec(&output_shmem->ref_count);
+err0:
+ if (input_shmem)
+ atomic_dec(&input_shmem->ref_count);
+
+ return false;
+}
+
+/*------------------------------------------------------------------------- */
+
+/*
+ *Post-process the client command (crypto update operation),
+ *i.e. copy the result into the user output buffer and release the resources.
+ *THIS FUNCTION IS CALLED FROM THE USER THREAD (ioctl).
+ */
+static void tf_crypto_write_answer(
+ struct cus_context *cus,
+ struct cus_params *params,
+ struct tf_answer_invoke_client_command *answer)
+{
+ u32 error = S_SUCCESS;
+
+ dprintk(KERN_INFO
+ "tf_crypto_write_answer(%p) : Session=0x%x\n",
+ cus, cus->client_session);
+
+ /* Generate the answer */
+ answer->message_size =
+ (sizeof(struct tf_answer_invoke_client_command) -
+ sizeof(struct tf_answer_header)) / 4;
+ answer->message_type = TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND;
+ answer->error_origin = TF_ORIGIN_TRUSTED_APP;
+ answer->operation_id = 0;
+ answer->error_code = error;
+ answer->answers[1].size.size = params->output_data_length;
+}
+
+/*------------------------------------------------------------------------- */
+
+int tf_crypto_try_shortcuted_update(struct tf_connection *connection,
+ struct tf_command_invoke_client_command *command,
+ struct tf_answer_invoke_client_command *answer)
+{
+ struct cus_context *cus = NULL;
+
+ if (tf_crypto_is_shortcuted_command(connection,
+ (struct tf_command_invoke_client_command *) command,
+ &cus, false)) {
+ u32 hwa_id = cus->hwa_id;
+
+ /* Lock HWA */
+ tf_crypto_lock_hwa(hwa_id, LOCK_HWA);
+
+ if (tf_crypto_is_shortcuted_command(connection,
+ command,
+ &cus, true)) {
+ struct cus_params cus_params;
+
+ memset(&cus_params, 0, sizeof(cus_params));
+
+ if (!tf_crypto_parse_command_message(
+ connection,
+ cus,
+ command,
+ &cus_params)) {
+ /* Decrement CUS context use count */
+ cus->use_count--;
+
+ /* Release HWA lock */
+ tf_crypto_lock_hwa(cus->hwa_id,
+ UNLOCK_HWA);
+
+ return -1;
+ }
+
+ /* Perform the update in public <=> THE shortcut */
+ if (!tf_crypto_update(cus, &cus_params)) {
+ /* Decrement CUS context use count */
+ cus->use_count--;
+
+ /* Release HWA lock */
+ tf_crypto_lock_hwa(cus->hwa_id,
+ UNLOCK_HWA);
+
+ return -1;
+ }
+
+ /* Write answer message */
+ tf_crypto_write_answer(cus,
+ &cus_params, answer);
+
+ /* Decrement registered shmems use count if needed */
+ if (cus_params.input_shmem)
+ atomic_dec(&cus_params.input_shmem->ref_count);
+ if (cus_params.output_shmem)
+ atomic_dec(&cus_params.output_shmem->ref_count);
+
+ /* Decrement CUS context use count */
+ cus->use_count--;
+
+ tf_crypto_lock_hwa(cus->hwa_id,
+ UNLOCK_HWA);
+ } else {
+ tf_crypto_lock_hwa(hwa_id, UNLOCK_HWA);
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*------------------------------------------------------------------------- */
+
+void tf_crypto_wait_for_ready_bit_infinitely(u32 *reg, u32 bit)
+{
+ while (!(INREG32(reg) & bit))
+ ;
+}
+
+/*------------------------------------------------------------------------- */
+
+u32 tf_crypto_wait_for_ready_bit(u32 *reg, u32 bit)
+{
+ u32 timeoutCounter = PUBLIC_CRYPTO_TIMEOUT_CONST;
+
+ while ((!(INREG32(reg) & bit)) && ((--timeoutCounter) != 0))
+ ;
+
+ if (timeoutCounter == 0)
+ return PUBLIC_CRYPTO_ERR_TIMEOUT;
+
+ return PUBLIC_CRYPTO_OPERATION_SUCCESS;
+}
+
+/*------------------------------------------------------------------------- */
+
+static DEFINE_SPINLOCK(clk_lock);
+
+void tf_crypto_disable_clock(uint32_t clock_paddr)
+{
+ u32 *clock_reg;
+ u32 val;
+ unsigned long flags;
+
+ dprintk(KERN_INFO "tf_crypto_disable_clock: " \
+ "clock_paddr=0x%08X\n",
+ clock_paddr);
+
+ /* Ensure none concurrent access when changing clock registers */
+ spin_lock_irqsave(&clk_lock, flags);
+
+ clock_reg = (u32 *)IO_ADDRESS(clock_paddr);
+
+ val = __raw_readl(clock_reg);
+ val &= ~(0x3);
+ __raw_writel(val, clock_reg);
+
+ /* Wait for clock to be fully disabled */
+ while ((__raw_readl(clock_reg) & 0x30000) == 0)
+ ;
+
+ spin_unlock_irqrestore(&clk_lock, flags);
+
+ tf_l4sec_clkdm_allow_idle(true);
+}
+
+/*------------------------------------------------------------------------- */
+
+void tf_crypto_enable_clock(uint32_t clock_paddr)
+{
+ u32 *clock_reg;
+ u32 val;
+ unsigned long flags;
+
+ dprintk(KERN_INFO "tf_crypto_enable_clock: " \
+ "clock_paddr=0x%08X\n",
+ clock_paddr);
+
+ tf_l4sec_clkdm_wakeup(true);
+
+ /* Ensure none concurrent access when changing clock registers */
+ spin_lock_irqsave(&clk_lock, flags);
+
+ clock_reg = (u32 *)IO_ADDRESS(clock_paddr);
+
+ val = __raw_readl(clock_reg);
+ val |= 0x2;
+ __raw_writel(val, clock_reg);
+
+ /* Wait for clock to be fully enabled */
+ while ((__raw_readl(clock_reg) & 0x30000) != 0)
+ ;
+
+ spin_unlock_irqrestore(&clk_lock, flags);
+}
+
+/*------------------------------------------------------------------------- */
+/* CUS RPCs */
+/*------------------------------------------------------------------------- */
+/*
+ * This RPC is used by the secure world to install a new shortcut. Optionally,
+ * for AES or DES/3DES operations, it can also lock the accelerator so that the
+ * secure world can install a new key in it.
+ */
+static int tf_crypto_install_shortcut_lock_hwa(
+ u32 rpc_command, void *rpc_shared_buffer)
+{
+ struct cus_context *cus = NULL;
+ struct tf_connection *connection = NULL;
+
+ /* Reference the input/ouput data */
+ struct rpc_install_shortcut_lock_accelerator_out *install_cus_out =
+ rpc_shared_buffer;
+ struct rpc_install_shortcut_lock_accelerator_in *install_cus_in =
+ rpc_shared_buffer;
+
+ dprintk(KERN_INFO "tf_crypto_install_shortcut_lock_hwa: "
+ "rpc_command=0x%08x; hwa_id=0x%08x\n",
+ rpc_command, install_cus_in->hwa_id);
+
+ connection = (struct tf_connection *)
+ install_cus_in->device_context_id;
+
+ if (connection == NULL) {
+ dprintk(KERN_INFO
+ "tf_crypto_install_shortcut_lock_hwa: "
+ "DeviceContext 0x%08x does not exist, "
+ "cannot create Shortcut\n",
+ install_cus_in->device_context_id);
+ install_cus_out->error = -1;
+ return 0;
+ }
+
+ /*
+ * Allocate a shortcut context. If the allocation fails,
+ * return S_ERROR_OUT_OF_MEMORY error code
+ */
+ cus = (struct cus_context *)
+ internal_kmalloc(sizeof(*cus), GFP_KERNEL);
+ if (cus == NULL) {
+ dprintk(KERN_ERR
+ "tf_crypto_install_shortcut_lock_hwa: "\
+ "Out of memory for public session\n");
+ install_cus_out->error = S_ERROR_OUT_OF_MEMORY;
+ return 0;
+ }
+
+ memset(cus, 0, sizeof(*cus));
+
+ /*setup the shortcut */
+ cus->magic_number = CUS_CONTEXT_MAGIC;
+ cus->client_session = install_cus_in->client_session;
+ cus->command_id = install_cus_in->command_id;
+ cus->hwa_id = install_cus_in->hwa_id;
+ cus->hwa_ctrl = install_cus_in->hwa_ctrl;
+ cus->key_context = install_cus_in->key_context;
+ cus->use_count = 0;
+ cus->suspended = false;
+
+ memcpy(&cus->operation_state,
+ &install_cus_in->operation_state,
+ sizeof(union tf_crypto_operation_state));
+
+ /*lock the shortcut_list_lock for this device context */
+ spin_lock(&connection->shortcut_list_lock);
+
+ /*Insert the shortcut in the list of shortcuts in the device context */
+ list_add(&(cus->list), &(connection->shortcut_list));
+
+ /*release shortcut_list_lock */
+ spin_unlock(&connection->shortcut_list_lock);
+
+ /*fill the output structure */
+ install_cus_out->shortcut_id = (u32) cus;
+ install_cus_out->error = S_SUCCESS;
+
+ /*If the L bit is true, then:
+ * Enter the accelerator critical section. If an update is currently in
+ * progress on the accelerator (using g_hXXXKeyContext key), this will
+ * wait until the update has completed. This is call when secure wants
+ * to install a key in HWA, once it is done secure world will release
+ * the lock. For SHA (activate shortcut is always called without LOCK
+ * fag):do nothing
+ */
+ if ((rpc_command & RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR_LOCK) != 0) {
+ /*Lock the HWA */
+ tf_crypto_lock_hwa(cus->hwa_id, LOCK_HWA);
+ }
+
+ dprintk(KERN_INFO
+ "tf_crypto_install_shortcut_lock_hwa: Done\n");
+
+ return S_SUCCESS;
+}
+
+/*------------------------------------------------------------------------- */
+
+/*
+ * This RPC is used to perform one or several of the following operations
+ * - Lock one or several accelerators for the exclusive use by the secure world,
+ * either because it is going to be switched to secure or because a new key is
+ * going to be loaded in the accelerator
+ * - Suspend a shortcut, i.e., make it temporarily unavailable to the public
+ * world. This is used when a secure update is going to be performed on the
+ * operation. The answer to the RPC then contains the operation state
+ * necessary for the secure world to do the update.
+ * - Uninstall the shortcut
+ */
+static int tf_crypto_lock_hwas_suspend_shortcut(
+ u32 rpc_command, void *rpc_shared_buffer)
+{
+ u32 target_shortcut;
+ struct cus_context *cus = NULL;
+ struct tf_connection *connection = NULL;
+
+ /*reference the input/ouput data */
+ struct rpc_lock_hwa_suspend_shortcut_out *suspend_cus_out =
+ rpc_shared_buffer;
+ struct rpc_lock_hwa_suspend_shortcut_in *suspend_cus_in =
+ rpc_shared_buffer;
+
+ dprintk(KERN_INFO
+ "tf_crypto_lock_hwas_suspend_shortcut: "\
+ "suspend_cus_in=0x%08x; shortcut_id=0x%08x\n",
+ suspend_cus_in->shortcut_id, (u32)suspend_cus_in);
+
+ target_shortcut = suspend_cus_in->shortcut_id;
+
+ /*lock HWAs */
+ tf_crypto_lock_hwas(rpc_command, LOCK_HWA);
+
+ /*if suspend_cus_in->shortcut_id != 0 and if rpc_command.S != 0,
+ then, suspend shortcut */
+ if ((target_shortcut != 0) && ((rpc_command &
+ RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_SUSPEND) != 0)) {
+ /*reference the CUSContext */
+ cus = (struct cus_context *)
+ suspend_cus_in->shortcut_id;
+
+ /*preventive check1: return if shortcut does not exist */
+ connection = tf_get_device_context(cus);
+ if (connection == NULL) {
+ dprintk(KERN_INFO
+ "tf_crypto_lock_hwas_suspend_shortcut: "\
+ "shortcut_id=0x%08x does not exist, cannot suspend "\
+ "Shortcut\n",
+ suspend_cus_in->shortcut_id);
+ return -1;
+ }
+
+loop_on_suspend:
+ /*lock shortcut_list_lock associated with the
+ *device context */
+ spin_lock(&connection->shortcut_list_lock);
+
+ /*Suspend shortcut */
+ cus->suspended = true;
+
+ if (cus->use_count != 0) {
+ /*release shortcut_list_lock */
+ spin_unlock(&connection->
+ shortcut_list_lock);
+ schedule();
+ goto loop_on_suspend;
+ }
+
+ /*Copy the operation state data stored in CUS Context into the
+ *answer to the RPC output assuming that HWA register has been
+ *saved at update time */
+ memcpy(&suspend_cus_out->operation_state,
+ &cus->operation_state,
+ sizeof(union tf_crypto_operation_state));
+
+ /*Uninstall shortcut if requiered */
+ if ((rpc_command &
+ RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT_UNINSTALL) != 0) {
+ dprintk(KERN_INFO
+ "tf_crypto_lock_hwas_suspend_shortcut:"\
+ "Uninstall 0x%08x\n",
+ target_shortcut);
+ list_del(&(cus->list));
+ /*list_del only remove the item in the list, the
+ *memory must be free afterward */
+ /*release the lock before calling internal_kfree */
+ spin_unlock(&connection->
+ shortcut_list_lock);
+ if (cus != NULL)
+ internal_kfree(cus);
+ return 0;
+ }
+
+ /*release shortcut_list_lock */
+ spin_unlock(&connection->shortcut_list_lock);
+ }
+
+ return 0;
+}
+
+/*------------------------------------------------------------------------- */
+
+/*
+ * This RPC is used to perform one or several of the following operations:
+ * - Resume a shortcut previously suspended
+ * - Inform the public driver of the new keys installed in the DES and AES
+ * accelerators
+ * - Unlock some of the accelerators
+ */
+static int tf_crypto_resume_shortcut_unlock_hwas(
+ u32 rpc_command, void *rpc_shared_buffer)
+{
+ struct tf_device *dev = tf_get_device();
+ struct tf_connection *connection = NULL;
+ struct cus_context *cus = NULL;
+
+ /*reference the input data */
+ struct rpc_resume_shortcut_unlock_hwa_in *resume_cus_in =
+ rpc_shared_buffer;
+
+ dprintk(KERN_INFO
+ "tf_crypto_resume_shortcut_unlock_hwas\n"
+ "rpc_command=0x%08x\nshortcut_id=0x%08x\n",
+ rpc_command, resume_cus_in->shortcut_id);
+
+ /*if shortcut_id not 0 resume the shortcut and unlock HWA
+ else only unlock HWA */
+ if (resume_cus_in->shortcut_id != 0) {
+ /*reference the CUSContext */
+ cus = (struct cus_context *)
+ resume_cus_in->shortcut_id;
+
+ /*preventive check1: return if shortcut does not exist
+ *else, points to the public crypto monitor (inside the device
+ *context) */
+ connection = tf_get_device_context(cus);
+ if (connection == NULL) {
+ dprintk(KERN_INFO
+ "tf_crypto_resume_shortcut_unlock_hwas(...):"\
+ "shortcut_id 0x%08x does not exist, cannot suspend "\
+ "Shortcut\n",
+ resume_cus_in->shortcut_id);
+ return -1;
+ }
+
+ /*if S set and shortcut not yet suspended */
+ if ((cus->suspended) &&
+ ((rpc_command &
+ RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_RESUME) != 0)){
+ /*Write operation_stateData in the shortcut context */
+ memcpy(&cus->operation_state,
+ &resume_cus_in->operation_state,
+ sizeof(union tf_crypto_operation_state));
+ /*resume the shortcut */
+ cus->suspended = false;
+ }
+ }
+
+ /*
+ * If A is set: Atomically set aes1_key_context to
+ * aes1_key_context
+ */
+ if ((rpc_command &
+ RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1) != 0) {
+ dev->aes1_key_context =
+ resume_cus_in->aes1_key_context;
+ }
+
+ /*
+ * If D is set:
+ * Atomically set des_key_context to des_key_context
+ */
+ if ((rpc_command &
+ RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES) != 0) {
+ dev->des_key_context =
+ resume_cus_in->des_key_context;
+ }
+
+ /* H is never set by the PA: Atomically set sham1_is_public to true */
+ dev->sham1_is_public = true;
+
+ /* Unlock HWAs according rpc_command */
+ tf_crypto_lock_hwas(rpc_command, UNLOCK_HWA);
+
+ return 0;
+}
+
+/*------------------------------------------------------------------------- */
+
+/*
+ * This RPC is used to notify the public driver that the key in the AES, DES
+ * accelerators has been cleared. This happens only when the key is no longer
+ * referenced by any shortcuts. So, it is guaranteed that no-one has entered the
+ * accelerators critical section and there is no need to enter it to implement
+ * this RPC.
+ */
+static int tf_crypto_clear_global_key_context(
+ u32 rpc_command, void *rpc_shared_buffer)
+{
+ struct tf_device *dev = tf_get_device();
+
+ /*
+ * If A is set: Atomically set aes1_key_context to 0
+ */
+ if ((rpc_command &
+ RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_AES1) != 0) {
+ dev->aes1_key_context = 0;
+ }
+
+ /*
+ *If D is set: Atomically set des_key_context to 0
+ */
+ if ((rpc_command &
+ RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS_UNLOCK_DES) != 0) {
+ dev->des_key_context = 0;
+ }
+
+ return 0;
+}
+
+/*------------------------------------------------------------------------- */
+/*
+ * Execute a public crypto related RPC
+ */
+
+int tf_crypto_execute_rpc(u32 rpc_command, void *rpc_shared_buffer)
+{
+ switch (rpc_command & RPC_CRYPTO_COMMAND_MASK) {
+ case RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR:
+ dprintk(KERN_INFO "RPC_INSTALL_SHORTCUT_LOCK_ACCELERATOR\n");
+ return tf_crypto_install_shortcut_lock_hwa(
+ rpc_command, rpc_shared_buffer);
+
+ case RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT:
+ dprintk(KERN_INFO "RPC_LOCK_ACCELERATORS_SUSPEND_SHORTCUT\n");
+ return tf_crypto_lock_hwas_suspend_shortcut(
+ rpc_command, rpc_shared_buffer);
+
+ case RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS:
+ dprintk(KERN_INFO "RPC_RESUME_SHORTCUT_UNLOCK_ACCELERATORS\n");
+ return tf_crypto_resume_shortcut_unlock_hwas(
+ rpc_command, rpc_shared_buffer);
+
+ case RPC_CLEAR_GLOBAL_KEY_CONTEXT:
+ dprintk(KERN_INFO "RPC_CLEAR_GLOBAL_KEY_CONTEXT\n");
+ return tf_crypto_clear_global_key_context(
+ rpc_command, rpc_shared_buffer);
+ }
+
+ return -1;
+}