diff options
Diffstat (limited to 'security/smc/tf_crypto.c')
-rw-r--r-- | security/smc/tf_crypto.c | 1278 |
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; +} |