/* Copyright (c) 2012, The Linux Foundation. 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 and * only 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. * */ #define KMSG_COMPONENT "SMCMOD" #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static DEFINE_MUTEX(ioctl_lock); #define SMCMOD_SVC_DEFAULT (0) #define SMCMOD_SVC_CRYPTO (1) #define SMCMOD_CRYPTO_CMD_CIPHER (1) #define SMCMOD_CRYPTO_CMD_MSG_DIGEST_FIXED (2) #define SMCMOD_CRYPTO_CMD_MSG_DIGEST (3) /** * struct smcmod_cipher_scm_req - structure for sending the cipher cmd to * scm_call. * * @algorithm - specifies cipher algorithm * @operation - specifies encryption or decryption. * @mode - specifies cipher mode. * @key_phys_addr - physical address for key buffer. * @key_size - key size in bytes. * @plain_text_phys_addr - physical address for plain text buffer. * @plain_text_size - size of plain text in bytes. * @cipher_text_phys_addr - physical address for cipher text buffer. * @cipher_text_size - cipher text size in bytes. * @init_vector_phys_addr - physical address for init vector buffer. * @init_vector_size - size of initialization vector in bytes. */ struct smcmod_cipher_scm_req { uint32_t algorithm; uint32_t operation; uint32_t mode; uint32_t key_phys_addr; uint32_t key_size; uint32_t plain_text_phys_addr; uint32_t plain_text_size; uint32_t cipher_text_phys_addr; uint32_t cipher_text_size; uint32_t init_vector_phys_addr; uint32_t init_vector_size; }; /** * struct smcmod_msg_digest_scm_req - structure for sending message digest * to scm_call. * * @algorithm - specifies the cipher algorithm. * @key_phys_addr - physical address of key buffer. * @key_size - hash key size in bytes. * @input_phys_addr - physical address of input buffer. * @input_size - input data size in bytes. * @output_phys_addr - physical address of output buffer. * @output_size - size of output buffer in bytes. * @verify - indicates whether to verify the hash value. */ struct smcmod_msg_digest_scm_req { uint32_t algorithm; uint32_t key_phys_addr; uint32_t key_size; uint32_t input_phys_addr; uint32_t input_size; uint32_t output_phys_addr; uint32_t output_size; uint8_t verify; } __packed; static void smcmod_inv_range(unsigned long start, unsigned long end) { uint32_t cacheline_size; uint32_t ctr; /* get cache line size */ asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr)); cacheline_size = 4 << ((ctr >> 16) & 0xf); /* invalidate the range */ start = round_down(start, cacheline_size); end = round_up(end, cacheline_size); while (start < end) { asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start) : "memory"); start += cacheline_size; } mb(); isb(); } static int smcmod_ion_fd_to_phys(int32_t fd, struct ion_client *ion_clientp, struct ion_handle **ion_handlep, uint32_t *phys_addrp, size_t *sizep) { int ret = 0; /* sanity check args */ if ((fd < 0) || IS_ERR_OR_NULL(ion_clientp) || IS_ERR_OR_NULL(ion_handlep) || IS_ERR_OR_NULL(phys_addrp) || IS_ERR_OR_NULL(sizep)) return -EINVAL; /* import the buffer fd */ *ion_handlep = ion_import_dma_buf(ion_clientp, fd); /* sanity check the handle */ if (IS_ERR_OR_NULL(*ion_handlep)) return -EINVAL; /* get the physical address */ ret = ion_phys(ion_clientp, *ion_handlep, (ion_phys_addr_t *)phys_addrp, sizep); return ret; } static int smcmod_send_buf_cmd(struct smcmod_buf_req *reqp) { int ret = 0; struct ion_client *ion_clientp = NULL; struct ion_handle *ion_cmd_handlep = NULL; struct ion_handle *ion_resp_handlep = NULL; void *cmd_vaddrp = NULL; void *resp_vaddrp = NULL; unsigned long cmd_buf_size = 0; unsigned long resp_buf_size = 0; /* sanity check the argument */ if (IS_ERR_OR_NULL(reqp)) return -EINVAL; /* sanity check the fds */ if (reqp->ion_cmd_fd < 0) return -EINVAL; /* create an ion client */ ion_clientp = msm_ion_client_create(UINT_MAX, "smcmod"); /* check for errors */ if (IS_ERR_OR_NULL(ion_clientp)) return -EINVAL; /* import the command buffer fd */ ion_cmd_handlep = ion_import_dma_buf(ion_clientp, reqp->ion_cmd_fd); /* sanity check the handle */ if (IS_ERR_OR_NULL(ion_cmd_handlep)) { ret = -EINVAL; goto buf_cleanup; } /* retrieve the size of the buffer */ if (ion_handle_get_size(ion_clientp, ion_cmd_handlep, &cmd_buf_size) < 0) { ret = -EINVAL; goto buf_cleanup; } /* ensure that the command buffer size is not * greater than the size of the buffer. */ if (reqp->cmd_len > cmd_buf_size) { ret = -EINVAL; goto buf_cleanup; } /* map the area to get a virtual address */ cmd_vaddrp = ion_map_kernel(ion_clientp, ion_cmd_handlep); /* sanity check the address */ if (IS_ERR_OR_NULL(cmd_vaddrp)) { ret = -EINVAL; goto buf_cleanup; } /* check if there is a response buffer */ if (reqp->ion_resp_fd >= 0) { /* import the handle */ ion_resp_handlep = ion_import_dma_buf(ion_clientp, reqp->ion_resp_fd); /* sanity check the handle */ if (IS_ERR_OR_NULL(ion_resp_handlep)) { ret = -EINVAL; goto buf_cleanup; } /* retrieve the size of the buffer */ if (ion_handle_get_size(ion_clientp, ion_resp_handlep, &resp_buf_size) < 0) { ret = -EINVAL; goto buf_cleanup; } /* ensure that the command buffer size is not * greater than the size of the buffer. */ if (reqp->resp_len > resp_buf_size) { ret = -EINVAL; goto buf_cleanup; } /* map the area to get a virtual address */ resp_vaddrp = ion_map_kernel(ion_clientp, ion_resp_handlep); /* sanity check the address */ if (IS_ERR_OR_NULL(resp_vaddrp)) { ret = -EINVAL; goto buf_cleanup; } } /* call scm function to switch to secure world */ reqp->return_val = scm_call(reqp->service_id, reqp->command_id, cmd_vaddrp, reqp->cmd_len, resp_vaddrp, reqp->resp_len); buf_cleanup: /* if the client and handle(s) are valid, free them */ if (!IS_ERR_OR_NULL(ion_clientp)) { if (!IS_ERR_OR_NULL(ion_cmd_handlep)) { if (!IS_ERR_OR_NULL(cmd_vaddrp)) ion_unmap_kernel(ion_clientp, ion_cmd_handlep); ion_free(ion_clientp, ion_cmd_handlep); } if (!IS_ERR_OR_NULL(ion_resp_handlep)) { if (!IS_ERR_OR_NULL(resp_vaddrp)) ion_unmap_kernel(ion_clientp, ion_resp_handlep); ion_free(ion_clientp, ion_resp_handlep); } ion_client_destroy(ion_clientp); } return ret; } static int smcmod_send_cipher_cmd(struct smcmod_cipher_req *reqp) { int ret = 0; struct smcmod_cipher_scm_req scm_req; struct ion_client *ion_clientp = NULL; struct ion_handle *ion_key_handlep = NULL; struct ion_handle *ion_plain_handlep = NULL; struct ion_handle *ion_cipher_handlep = NULL; struct ion_handle *ion_iv_handlep = NULL; size_t size = 0; if (IS_ERR_OR_NULL(reqp)) return -EINVAL; /* sanity check the fds */ if ((reqp->ion_plain_text_fd < 0) || (reqp->ion_cipher_text_fd < 0) || (reqp->ion_init_vector_fd < 0)) return -EINVAL; /* create an ion client */ ion_clientp = msm_ion_client_create(UINT_MAX, "smcmod"); /* check for errors */ if (IS_ERR_OR_NULL(ion_clientp)) return -EINVAL; /* fill in the scm request structure */ scm_req.algorithm = reqp->algorithm; scm_req.operation = reqp->operation; scm_req.mode = reqp->mode; scm_req.key_phys_addr = 0; scm_req.key_size = reqp->key_size; scm_req.plain_text_size = reqp->plain_text_size; scm_req.cipher_text_size = reqp->cipher_text_size; scm_req.init_vector_size = reqp->init_vector_size; if (!reqp->key_is_null) { /* import the key buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_key_fd, ion_clientp, &ion_key_handlep, &scm_req.key_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the key size is not * greater than the size of the buffer. */ if (reqp->key_size > size) { ret = -EINVAL; goto buf_cleanup; } } /* import the plain text buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_plain_text_fd, ion_clientp, &ion_plain_handlep, &scm_req.plain_text_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the plain text size is not * greater than the size of the buffer. */ if (reqp->plain_text_size > size) { ret = -EINVAL; goto buf_cleanup; } /* import the cipher text buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_cipher_text_fd, ion_clientp, &ion_cipher_handlep, &scm_req.cipher_text_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the cipher text size is not * greater than the size of the buffer. */ if (reqp->cipher_text_size > size) { ret = -EINVAL; goto buf_cleanup; } /* import the init vector buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_init_vector_fd, ion_clientp, &ion_iv_handlep, &scm_req.init_vector_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the init vector size is not * greater than the size of the buffer. */ if (reqp->init_vector_size > size) { ret = -EINVAL; goto buf_cleanup; } /* call scm function to switch to secure world */ reqp->return_val = scm_call(SMCMOD_SVC_CRYPTO, SMCMOD_CRYPTO_CMD_CIPHER, &scm_req, sizeof(scm_req), NULL, 0); /* for decrypt, plain text is the output, otherwise it's cipher text */ if (reqp->operation) { void *vaddrp = NULL; /* map the plain text region to get the virtual address */ vaddrp = ion_map_kernel(ion_clientp, ion_plain_handlep); if (IS_ERR_OR_NULL(vaddrp)) { ret = -EINVAL; goto buf_cleanup; } /* invalidate the range */ smcmod_inv_range((unsigned long)vaddrp, (unsigned long)(vaddrp + scm_req.plain_text_size)); /* unmap the mapped area */ ion_unmap_kernel(ion_clientp, ion_plain_handlep); } else { void *vaddrp = NULL; /* map the cipher text region to get the virtual address */ vaddrp = ion_map_kernel(ion_clientp, ion_cipher_handlep); if (IS_ERR_OR_NULL(vaddrp)) { ret = -EINVAL; goto buf_cleanup; } /* invalidate the range */ smcmod_inv_range((unsigned long)vaddrp, (unsigned long)(vaddrp + scm_req.cipher_text_size)); /* unmap the mapped area */ ion_unmap_kernel(ion_clientp, ion_cipher_handlep); } buf_cleanup: /* if the client and handles are valid, free them */ if (!IS_ERR_OR_NULL(ion_clientp)) { if (!IS_ERR_OR_NULL(ion_key_handlep)) ion_free(ion_clientp, ion_key_handlep); if (!IS_ERR_OR_NULL(ion_plain_handlep)) ion_free(ion_clientp, ion_plain_handlep); if (!IS_ERR_OR_NULL(ion_cipher_handlep)) ion_free(ion_clientp, ion_cipher_handlep); if (!IS_ERR_OR_NULL(ion_iv_handlep)) ion_free(ion_clientp, ion_iv_handlep); ion_client_destroy(ion_clientp); } return ret; } static int smcmod_send_msg_digest_cmd(struct smcmod_msg_digest_req *reqp) { int ret = 0; struct smcmod_msg_digest_scm_req scm_req; struct ion_client *ion_clientp = NULL; struct ion_handle *ion_key_handlep = NULL; struct ion_handle *ion_input_handlep = NULL; struct ion_handle *ion_output_handlep = NULL; size_t size = 0; void *vaddrp = NULL; if (IS_ERR_OR_NULL(reqp)) return -EINVAL; /* sanity check the fds */ if ((reqp->ion_input_fd < 0) || (reqp->ion_output_fd < 0)) return -EINVAL; /* create an ion client */ ion_clientp = msm_ion_client_create(UINT_MAX, "smcmod"); /* check for errors */ if (IS_ERR_OR_NULL(ion_clientp)) return -EINVAL; /* fill in the scm request structure */ scm_req.algorithm = reqp->algorithm; scm_req.key_phys_addr = 0; scm_req.key_size = reqp->key_size; scm_req.input_size = reqp->input_size; scm_req.output_size = reqp->output_size; scm_req.verify = 0; if (!reqp->key_is_null) { /* import the key buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_key_fd, ion_clientp, &ion_key_handlep, &scm_req.key_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the key size is not * greater than the size of the buffer. */ if (reqp->key_size > size) { ret = -EINVAL; goto buf_cleanup; } } /* import the input buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_input_fd, ion_clientp, &ion_input_handlep, &scm_req.input_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the input size is not * greater than the size of the buffer. */ if (reqp->input_size > size) { ret = -EINVAL; goto buf_cleanup; } /* import the output buffer and get the physical address */ ret = smcmod_ion_fd_to_phys(reqp->ion_output_fd, ion_clientp, &ion_output_handlep, &scm_req.output_phys_addr, &size); if (ret < 0) goto buf_cleanup; /* ensure that the output size is not * greater than the size of the buffer. */ if (reqp->output_size > size) { ret = -EINVAL; goto buf_cleanup; } /* call scm function to switch to secure world */ if (reqp->fixed_block) reqp->return_val = scm_call(SMCMOD_SVC_CRYPTO, SMCMOD_CRYPTO_CMD_MSG_DIGEST_FIXED, &scm_req, sizeof(scm_req), NULL, 0); else reqp->return_val = scm_call(SMCMOD_SVC_CRYPTO, SMCMOD_CRYPTO_CMD_MSG_DIGEST, &scm_req, sizeof(scm_req), NULL, 0); /* map the output region to get the virtual address */ vaddrp = ion_map_kernel(ion_clientp, ion_output_handlep); if (IS_ERR_OR_NULL(vaddrp)) { ret = -EINVAL; goto buf_cleanup; } /* invalidate the range */ smcmod_inv_range((unsigned long)vaddrp, (unsigned long)(vaddrp + scm_req.output_size)); /* unmap the mapped area */ ion_unmap_kernel(ion_clientp, ion_output_handlep); buf_cleanup: /* if the client and handles are valid, free them */ if (!IS_ERR_OR_NULL(ion_clientp)) { if (!IS_ERR_OR_NULL(ion_key_handlep)) ion_free(ion_clientp, ion_key_handlep); if (!IS_ERR_OR_NULL(ion_input_handlep)) ion_free(ion_clientp, ion_input_handlep); if (!IS_ERR_OR_NULL(ion_output_handlep)) ion_free(ion_clientp, ion_output_handlep); ion_client_destroy(ion_clientp); } return ret; } static long smcmod_ioctl(struct file *file, unsigned cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int ret = 0; /* sanity check */ if (!argp) return -EINVAL; /* * The SMC instruction should only be initiated by one process * at a time, hence the critical section here. Note that this * does not prevent user space from modifying the * allocated buffer contents. Extra steps are needed to * prevent that from happening. */ mutex_lock(&ioctl_lock); switch (cmd) { case SMCMOD_IOCTL_SEND_REG_CMD: { struct smcmod_reg_req req; /* copy struct from user */ if (copy_from_user((void *)&req, argp, sizeof(req))) { ret = -EFAULT; goto cleanup; } /* call the correct scm function to switch to secure * world */ if (req.num_args == 1) { req.return_val = scm_call_atomic1(req.service_id, req.command_id, req.args[0]); } else if (req.num_args == 2) { req.return_val = scm_call_atomic2(req.service_id, req.command_id, req.args[0], req.args[1]); } else { ret = -EINVAL; goto cleanup; } /* copy result back to user */ if (copy_to_user(argp, (void *)&req, sizeof(req))) { ret = -EFAULT; goto cleanup; } } break; /* This is an example of how to pass buffers to/from the secure * side using the ion driver. */ case SMCMOD_IOCTL_SEND_BUF_CMD: { struct smcmod_buf_req req; /* copy struct from user */ if (copy_from_user((void *)&req, argp, sizeof(req))) { ret = -EFAULT; goto cleanup; } /* send the command */ ret = smcmod_send_buf_cmd(&req); if (ret < 0) goto cleanup; /* copy result back to user */ if (copy_to_user(argp, (void *)&req, sizeof(req))) { ret = -EFAULT; goto cleanup; } } break; case SMCMOD_IOCTL_SEND_CIPHER_CMD: { struct smcmod_cipher_req req; /* copy struct from user */ if (copy_from_user((void *)&req, argp, sizeof(req))) { ret = -EFAULT; goto cleanup; } ret = smcmod_send_cipher_cmd(&req); if (ret < 0) goto cleanup; /* copy result back to user */ if (copy_to_user(argp, (void *)&req, sizeof(req))) { ret = -EFAULT; goto cleanup; } } break; case SMCMOD_IOCTL_SEND_MSG_DIGEST_CMD: { struct smcmod_msg_digest_req req; /* copy struct from user */ if (copy_from_user((void *)&req, argp, sizeof(req))) { ret = -EFAULT; goto cleanup; } ret = smcmod_send_msg_digest_cmd(&req); if (ret < 0) goto cleanup; /* copy result back to user */ if (copy_to_user(argp, (void *)&req, sizeof(req))) { ret = -EFAULT; goto cleanup; } } break; case SMCMOD_IOCTL_GET_VERSION: { uint32_t req; /* call scm function to switch to secure world */ req = scm_get_version(); /* copy result back to user */ if (copy_to_user(argp, (void *)&req, sizeof(req))) { ret = -EFAULT; goto cleanup; } } break; default: ret = -EINVAL; } cleanup: mutex_unlock(&ioctl_lock); return ret; } static int smcmod_open(struct inode *inode, struct file *file) { return 0; } static int smcmod_release(struct inode *inode, struct file *file) { return 0; } static const struct file_operations smcmod_fops = { .owner = THIS_MODULE, .unlocked_ioctl = smcmod_ioctl, .open = smcmod_open, .release = smcmod_release, }; static struct miscdevice smcmod_misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = SMCMOD_DEV, .fops = &smcmod_fops }; static int __init smcmod_init(void) { return misc_register(&smcmod_misc_dev); } static void __exit smcmod_exit(void) { misc_deregister(&smcmod_misc_dev); } MODULE_DESCRIPTION("Qualcomm SMC Module"); MODULE_LICENSE("GPL v2"); module_init(smcmod_init); module_exit(smcmod_exit);