| /* |
| * TrustZone Driver |
| * |
| * Copyright (C) 2016 Marvell Ltd. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * 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 <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/platform_device.h> |
| #include <linux/debugfs.h> |
| #include <linux/cdev.h> |
| #include <linux/uaccess.h> |
| #include <linux/sched.h> |
| #include <linux/list.h> |
| #include <linux/kthread.h> |
| #include <linux/mutex.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/vmalloc.h> |
| #include <linux/hw_random.h> |
| |
| #include "config.h" |
| #include "tz_log.h" |
| #include "tz_utils.h" |
| #include "tz_driver.h" |
| #include "tz_driver_private.h" |
| #include "tz_nw_ioctl.h" |
| #include "tz_nw_api.h" |
| #include "tz_nw_comm.h" |
| #include "tz_boot_cmd.h" |
| #include "tz_client_api.h" |
| #include "tee_sys_cmd.h" |
| #include "tee_mgr_cmd.h" |
| #include "tee_client_api.h" |
| #include "ree_sys_callback.h" |
| |
| /* FIXME: copy from tee_ta_mgr.h, need to keep same */ |
| #define TEE_SESSION_TASKID(sessionId) ((sessionId) >> 24) |
| |
| /* Helper macro used for checking return value from smc */ |
| #define SMC_RET(result, func_id) ({ \ |
| int __rc = 0; \ |
| if (result != 0) { \ |
| if (result == SMC_FUNC_ID_UNKNOWN) { \ |
| tz_error("not suppported func (%lu)", func_id); \ |
| __rc = -EINVAL; \ |
| } else { \ |
| tz_error("func(%lu) access denied", func_id); \ |
| __rc = -EACCES; \ |
| } \ |
| } \ |
| __rc; }) |
| |
| #define TZ_RNG_ENTROPY(x) (((x) * 8 * 10) >> 5) /* quality: 10/32 */ |
| #define HW_RNG_GET_DATA_ID (4) /* fastcall sub-command toto gett hwrng data */ |
| #define HW_RNG_GET_DATA_SIZE (512) |
| |
| static struct tzd_dev_file *kernel_dev_file; |
| static struct task_struct *tzd_hwrng_task; |
| static DECLARE_WAIT_QUEUE_HEAD(hwrng_wait_queue); |
| |
| static int tzd_resource_free(void *private_data) |
| { |
| struct tzd_dev_file *dev = (struct tzd_dev_file *) private_data; |
| if (unlikely(!dev)) |
| return -ENODEV; |
| tzd_shm_delete(dev); |
| kfree(dev); |
| return 0; |
| } |
| |
| static int tzd_mmap(struct file *filp, struct vm_area_struct *vma) |
| { |
| struct tzd_dev_file *dev = (struct tzd_dev_file *) filp->private_data; |
| int ret = 0; |
| |
| if (unlikely(!dev)) |
| return -ENODEV; |
| |
| ret = tzd_shm_mmap(dev, vma); |
| if (ret) { |
| tz_error("tz mmap error at 0x%lx", vma->vm_pgoff << PAGE_SHIFT); |
| return ret; |
| } |
| |
| ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, |
| vma->vm_end - vma->vm_start, vma->vm_page_prot); |
| if (ret) { |
| tz_error("tz mmap remap_pfn_range error!"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int tzd_alloc_mem(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| unsigned long __user *argp = (unsigned long __user *) arg; |
| unsigned long size; |
| void *p; |
| |
| if (get_user(size, argp)) { |
| tz_error("copy from user failed!"); |
| return -EFAULT; |
| } |
| |
| tz_debug("alloc_len = %lu\n", size); |
| |
| if (unlikely(!size)) { |
| tz_error("alloc mem length is 0!"); |
| return -EINVAL; |
| } |
| |
| p = tzd_shm_alloc(dev, size, GFP_KERNEL); |
| if (unlikely(!p)) { |
| tz_error("alloc mem failed on size %lu!", size); |
| return -ENOMEM; |
| } |
| |
| /* copy back the phy addr */ |
| if (put_user((unsigned long)p, argp)) { |
| tz_error("copy to user failed"); |
| tzd_shm_free(dev, p); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int tzd_free_mem(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| return tzd_shm_free(dev, (const void *)arg); |
| } |
| |
| int tzd_kernel_alloc_mem(void **va, void **pa, uint32_t alloc_len) |
| { |
| struct tzd_shm *mem_new; |
| |
| if (unlikely(!alloc_len)) { |
| tz_error("alloc mem length is 0!"); |
| return -EINVAL; |
| } |
| |
| mem_new = tzd_shm_new(kernel_dev_file, alloc_len, GFP_KERNEL); |
| if (!mem_new) { |
| tz_error("tzd_shm_new failed"); |
| return -ENOMEM; |
| } |
| |
| if (va) *va = mem_new->k_addr; |
| if (pa) *pa = mem_new->p_addr; |
| |
| return 0; |
| } |
| |
| void *tzd_phys_to_virt(void *call_info, void *pa) |
| { |
| struct tzd_dev_file *dev = call_info; |
| return tzd_shm_phys_to_virt(dev, pa); |
| } |
| |
| int tzd_kernel_free_mem(void *pa) |
| { |
| return tzd_shm_free(kernel_dev_file, pa); |
| } |
| |
| void *tzd_get_kernel_dev_file(void) |
| { |
| return (void *)kernel_dev_file; |
| } |
| |
| static inline int __is_kernel_va(unsigned long va) |
| { |
| return virt_addr_valid(va); |
| } |
| |
| static inline int __is_vmalloc_va(unsigned long va) |
| { |
| return (va <= VMALLOC_END && va >= VMALLOC_START); |
| } |
| |
| static inline int __is_user_va(unsigned long va) |
| { |
| #if defined(CONFIG_ARM) || defined(CONFIG_ARM64) |
| return (va < MODULES_VADDR); |
| #else |
| #warning "FIXME: assume kernel va starts from PAGE_OFFSET" |
| return (va < PAGE_OFFSET); |
| #endif |
| } |
| |
| int tz_get_meminfo(struct tz_mem_info *info) |
| { |
| int ret = 0; |
| |
| if (info->va == NULL) { |
| tz_error("virtual address is NULL!"); |
| ret = -1; |
| return ret; |
| } |
| |
| if (__is_kernel_va((unsigned long)info->va)) { |
| info->pa = (void *)virt_to_phys((void *)info->va); |
| info->attr = 0; |
| } else if (__is_vmalloc_va((unsigned long)info->va)) { |
| tz_error("virtual address is vmalloc, not support"); |
| info->pa = NULL; |
| info->attr = 0; |
| } else { |
| tz_error("virtual address is INVALID!"); |
| ret = -1; |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int tzd_get_meminfo(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_mem_info info; |
| unsigned long pte; |
| |
| if (copy_from_user((void*)&info, argp, sizeof(struct tz_mem_info))) { |
| tz_error("copy from user failed!"); |
| return -EFAULT; |
| } |
| |
| if (info.va == NULL) { |
| tz_error("user space address is NULL!"); |
| return -EFAULT; |
| } |
| |
| pte = tz_user_virt_to_pte(current->mm, (unsigned long)info.va); |
| if (pte) { |
| info.pa = (void *)((pte & PAGE_MASK) + |
| ((unsigned long)info.va & ~PAGE_MASK)); |
| info.attr = pte & ~PAGE_MASK; |
| } else { |
| info.pa = NULL; |
| info.attr = 0; |
| } |
| |
| if (copy_to_user(argp, &info, sizeof(struct tz_mem_info))) { |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int tzd_cmd_req(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| int call_id, ret; |
| struct tz_nw_comm cc; |
| struct tz_nw_task_client *tc; |
| |
| /* copy the command to communication channel */ |
| if (copy_from_user((void *)&cc, argp, sizeof(struct tz_nw_comm))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| |
| tc = tz_nw_task_client_get(cc.call.task_id); |
| call_id = current->pid; |
| ret = tz_nw_comm_invoke_command(tc, &cc, call_id, (void *)dev); |
| |
| /* copy back the communication data */ |
| if (copy_to_user(argp, &cc, sizeof(struct tz_nw_comm))) { |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| |
| return ret; |
| } |
| |
| static int tzd_fastcall_memmove(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_memmove_param param; |
| unsigned long func_id = SMC_FUNC_TOS_MEM_MOVE, result; |
| /* copy the command to communication channel */ |
| if (copy_from_user((void *)¶m, argp, sizeof(param))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| result = __smc(func_id, param.dst, param.src, param.size); |
| return SMC_RET(result, func_id); |
| } |
| |
| static int tzd_fastcall_hwrng_get_data(struct fastcall_generic_param *param, unsigned int data_size) |
| { |
| unsigned long func_id = SMC_FUNC_TOS_FASTCALL_GENERIC; |
| unsigned long result; |
| unsigned int total_size = 0; |
| |
| total_size = data_size + sizeof(struct fastcall_generic_param); |
| param->sub_cmd_id = HW_RNG_GET_DATA_ID; |
| param->param_len = data_size; |
| result = __smc(func_id, (void *)virt_to_phys(param), total_size); |
| |
| return SMC_RET(result, func_id); |
| } |
| |
| static int tzd_fastcall_secure_cache(struct tzd_dev_file *dev, |
| unsigned long arg, unsigned long func_id) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_cache_param param; |
| unsigned long result; |
| /* copy the command to communication channel */ |
| if (copy_from_user((void *)¶m, argp, sizeof(param))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| result = __smc(func_id, param.start, param.size); |
| return SMC_RET(result, func_id); |
| } |
| |
| static int tzd_fastcall_generic_cmd(struct tzd_dev_file *dev, |
| unsigned long arg, unsigned long func_id) |
| { |
| struct fastcall_generic_param __user *argp; |
| struct fastcall_generic_param param_header; |
| struct fastcall_generic_param *param; |
| struct tzd_shm *shm; |
| int total_param_len; |
| unsigned long result; |
| |
| argp = (struct fastcall_generic_param __user *)arg; |
| /* copy the command to communication channel */ |
| if (copy_from_user((void *)¶m_header, argp, |
| sizeof(param_header))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| |
| total_param_len = param_header.param_len + sizeof(param_header); |
| shm = tzd_shm_new(dev, total_param_len, GFP_KERNEL); |
| if (NULL == shm) { |
| tz_error("out of share memory"); |
| return -EFAULT; |
| } |
| param = (struct fastcall_generic_param *)shm->k_addr; |
| if (copy_from_user((void *)param, (void __user *)argp, |
| total_param_len)) { |
| tzd_shm_free(dev, shm->p_addr); |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| result = __smc(func_id, shm->p_addr, total_param_len); |
| if (copy_to_user((void __user *)argp->param, param->param, |
| param->param_len)) { |
| tzd_shm_free(dev, shm->p_addr); |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| tzd_shm_free(dev, shm->p_addr); |
| |
| return SMC_RET(result, func_id); |
| } |
| |
| static int tzd_hwrng_kthread(void *data) |
| { |
| unsigned int bytes_read = HW_RNG_GET_DATA_SIZE; |
| struct fastcall_generic_param *rng_buf = NULL; |
| |
| rng_buf = (struct fastcall_generic_param *)get_zeroed_page(GFP_KERNEL); |
| if (!rng_buf) { |
| tz_error("allocate memory failed"); |
| do_exit(0); |
| } |
| |
| while (!kthread_should_stop()) { |
| /* get tz hardware RNG data */ |
| if (tzd_fastcall_hwrng_get_data(rng_buf, bytes_read) != 0) { |
| tz_error("get hardware RNG data failed"); |
| wait_event_interruptible_timeout(hwrng_wait_queue, |
| kthread_should_stop(), |
| msecs_to_jiffies(1000)); |
| continue; |
| } |
| |
| /* sleep until entropy bits under write_wakeup_threshold */ |
| add_hwgenerator_randomness((void *)&rng_buf->param, bytes_read, |
| TZ_RNG_ENTROPY(bytes_read)); |
| } |
| |
| free_page((unsigned long)rng_buf); |
| |
| do_exit(0); |
| } |
| |
| static int tzd_create_instance(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_instance_param create_param; |
| if (copy_from_user((void *)&create_param, argp, |
| sizeof(create_param))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| create_param.result = |
| tz_invoke_command(0, TZ_TASK_ID_MGR, TZ_CMD_TEE_SYS, |
| create_param.param, &create_param.origin, |
| NULL, NULL, dev); |
| if (copy_to_user(argp, &create_param, sizeof(create_param))) { |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| static int tzd_destroy_instance(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_instance_param destroy_param; |
| |
| if (copy_from_user((void *)&destroy_param, argp, |
| sizeof(destroy_param))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| |
| destroy_param.result = |
| tz_invoke_command(0, TZ_TASK_ID_MGR, TZ_CMD_TEE_SYS, |
| destroy_param.param, &destroy_param.origin, |
| NULL, NULL, dev); |
| if (copy_to_user(argp, &destroy_param, sizeof(destroy_param))) { |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| struct tzd_session_info { |
| struct list_head head; |
| uint32_t param; |
| uint32_t session_id; |
| }; |
| |
| static int open_session(struct tzd_dev_file *dev, |
| struct tz_session_param *open_param) |
| { |
| if (unlikely(!dev)) |
| return -ENODEV; |
| if (unlikely(!open_param)) |
| return -EINVAL; |
| open_param->result = |
| tz_invoke_command(0, open_param->task_id, TZ_CMD_TEE_SYS, |
| open_param->param, &open_param->origin, |
| NULL, NULL, dev); |
| if (!open_param->result) { |
| struct tzd_session_info *new_info = NULL; |
| struct tee_comm_param *cmd; |
| TASysCmdOpenSessionParamExt *p; |
| cmd = tzd_shm_phys_to_virt(dev, |
| (void *)((unsigned long)open_param->param)); |
| p = (void *)cmd->param_ext; |
| |
| new_info = kzalloc(sizeof(*new_info), GFP_KERNEL); |
| if (!new_info) { |
| tz_error("kzalloc failed"); |
| return -ENOMEM; |
| } |
| |
| new_info->param = open_param->param; |
| new_info->session_id = p->sessionId; |
| |
| mutex_lock(&dev->tz_mutex); |
| list_add_tail(&new_info->head, &dev->session_list); |
| mutex_unlock(&dev->tz_mutex); |
| } |
| |
| return 0; |
| } |
| |
| static int close_session(struct tzd_dev_file *dev, |
| struct tz_session_param *close_param) |
| { |
| close_param->result = |
| tz_invoke_command(0, close_param->task_id, TZ_CMD_TEE_SYS, |
| close_param->param, &close_param->origin, |
| NULL, NULL, dev); |
| if (!close_param->result) { |
| struct tzd_session_info *temp_info; |
| struct tzd_session_info *temp_pos; |
| struct tee_comm_param *cmd; |
| cmd = tzd_shm_phys_to_virt(dev, |
| (void *)((unsigned long)close_param->param)); |
| |
| mutex_lock(&dev->tz_mutex); |
| list_for_each_entry_safe(temp_info, temp_pos, |
| &dev->session_list, head) { |
| if (temp_info->session_id == cmd->session_id) { |
| list_del(&temp_info->head); |
| if (temp_info) |
| kfree(temp_info); |
| break; |
| } |
| } |
| mutex_unlock(&dev->tz_mutex); |
| } |
| |
| return 0; |
| } |
| |
| static int tzd_open_session(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_session_param open_param; |
| int ret; |
| |
| if (copy_from_user((void *)&open_param, argp, sizeof(open_param))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| |
| ret = open_session(dev, &open_param); |
| if (ret) |
| return ret; |
| |
| if (copy_to_user(argp, &open_param, sizeof(open_param))) { |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int tzd_close_session(struct tzd_dev_file *dev, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| struct tz_session_param close_param; |
| |
| if (copy_from_user((void *)&close_param, argp, sizeof(close_param))) { |
| tz_error("copy from user failed"); |
| return -EFAULT; |
| } |
| |
| close_session(dev, &close_param); |
| |
| if (copy_to_user(argp, &close_param, sizeof(close_param))) { |
| tz_error("copy to user failed"); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| int tzd_kernel_create_instance(struct tz_instance_param *create_param) |
| { |
| create_param->result = |
| tzc_invoke_command(0, TZ_TASK_ID_MGR, TZ_CMD_TEE_SYS, |
| create_param->param, &create_param->origin, |
| NULL, NULL); |
| return create_param->result; |
| } |
| |
| int tzd_kernel_destroy_instance(struct tz_instance_param *destroy_param) |
| { |
| destroy_param->result = |
| tzc_invoke_command(0, TZ_TASK_ID_MGR, TZ_CMD_TEE_SYS, |
| destroy_param->param, &destroy_param->origin, |
| NULL, NULL); |
| return destroy_param->result; |
| } |
| |
| int tzd_kernel_open_session(struct tz_session_param *open_param) |
| { |
| return open_session(kernel_dev_file, open_param); |
| } |
| |
| int tzd_kernel_close_session(struct tz_session_param *close_param) |
| { |
| return close_session(kernel_dev_file, close_param); |
| } |
| |
| long tzd_ioctl(struct file *file, unsigned cmd, unsigned long arg) |
| { |
| int ret = -EINVAL; |
| struct tzd_dev_file *dev; |
| |
| dev = (struct tzd_dev_file *) file->private_data; |
| |
| switch (cmd) { |
| case TZ_CLIENT_IOCTL_CMD: |
| ret = tzd_cmd_req(dev, arg); |
| break; |
| |
| case TZ_CLIENT_IOCTL_FASTCALL_GENERIC_CMD: |
| ret = tzd_fastcall_generic_cmd(dev, arg, |
| SMC_FUNC_TOS_FASTCALL_GENERIC); |
| break; |
| |
| case TZ_CLIENT_IOCTL_FASTCALL_MEMMOVE: |
| ret = tzd_fastcall_memmove(dev, arg); |
| break; |
| |
| case TZ_CLIENT_IOCTL_FASTCALL_CACHE_CLEAN: |
| ret = tzd_fastcall_secure_cache(dev, arg, |
| SMC_FUNC_TOS_SECURE_CACHE_CLEAN); |
| break; |
| |
| case TZ_CLIENT_IOCTL_FASTCALL_CACHE_INVALIDATE: |
| ret = tzd_fastcall_secure_cache(dev, arg, |
| SMC_FUNC_TOS_SECURE_CACHE_INVALIDATE); |
| break; |
| |
| case TZ_CLIENT_IOCTL_FASTCALL_CACHE_FLUSH: |
| ret = tzd_fastcall_secure_cache(dev, arg, |
| SMC_FUNC_TOS_SECURE_CACHE_CLEAN_INVALIDATE); |
| break; |
| |
| case TZ_CLIENT_IOCTL_ALLOC_MEM: |
| ret = tzd_alloc_mem(dev, arg); |
| if (ret) |
| tz_error("failed tzd_alloc_mem: %d", ret); |
| break; |
| |
| case TZ_CLIENT_IOCTL_FREE_MEM: |
| ret = tzd_free_mem(dev, arg); |
| if (ret) |
| tz_error("failed tzd_free_mem: %d", ret); |
| break; |
| |
| case TZ_CLIENT_IOCTL_GET_MEMINFO: |
| ret = tzd_get_meminfo(dev, arg); |
| break; |
| |
| case TZ_CLIENT_IOCTL_OPEN_SESSION: |
| ret = tzd_open_session(dev, arg); |
| break; |
| |
| case TZ_CLIENT_IOCTL_CLOSE_SESSION: |
| ret = tzd_close_session(dev, arg); |
| break; |
| |
| case TZ_CLIENT_IOCTL_CREATE_INSTANCE: |
| ret = tzd_create_instance(dev, arg); |
| break; |
| |
| case TZ_CLIENT_IOCTL_DESTROY_INSTANCE: |
| ret = tzd_destroy_instance(dev, arg); |
| break; |
| |
| default: |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int tzd_open(struct inode *inode, struct file *file) |
| { |
| struct tzd_dev_file *new_dev; |
| |
| tz_debug("tzd_open"); |
| |
| new_dev = (struct tzd_dev_file*)kzalloc(sizeof(struct tzd_dev_file), GFP_KERNEL); |
| if (!new_dev) { |
| tz_error("kzalloc failed for new dev file allocation"); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&new_dev->dev_shm_head.shm_list); |
| |
| mutex_init(&new_dev->tz_mutex); |
| |
| INIT_LIST_HEAD(&new_dev->session_list); |
| |
| file->private_data = (void *)new_dev; |
| |
| return 0; |
| } |
| |
| static int tzd_release_session(struct tzd_dev_file *temp_dev_file, |
| struct tzd_session_info *info) |
| { |
| struct tee_comm_param *cmd; |
| uint32_t task_id; |
| uint32_t origin; |
| bool instance_dead = false; |
| struct tz_nw_task_client *tc; |
| int ret = 0; |
| |
| task_id = TEE_SESSION_TASKID(info->session_id); |
| tc = tz_nw_task_client_get(task_id); |
| |
| WARN_ON(!tc); |
| if (unlikely(!tc)) { |
| return -EINVAL; |
| } |
| /* if in callback, first stop callback */ |
| if (tc->state == TZ_NW_TASK_STATE_CALLBACK) { |
| struct tz_nw_comm cc; |
| |
| /* There may be a race-condition for the state. */ |
| cc.call.task_id = task_id; |
| cc.call.cmd_id = TZ_CMD_TEE_USER; |
| cc.call.param = info->param; |
| cc.callback.result = TZ_ERROR_TARGET_DEAD; |
| cc.callback.origin = TZ_ORIGIN_UNTRUSTED_APP; |
| tc->dead = true; |
| while (1) { |
| ret = tz_nw_comm_invoke_command(tc, &cc, tc->call_id, |
| temp_dev_file); |
| if (ret != TZ_PENDING) |
| break; |
| } |
| } |
| |
| cmd = tzd_shm_phys_to_virt_nolock(temp_dev_file, |
| (void *)((unsigned long)info->param)); |
| |
| /* FIXME: it introduce too much TEE code here */ |
| /* close session */ |
| memset(cmd, 0, TEE_COMM_PARAM_BASIC_SIZE); |
| cmd->cmd_id = TASYS_CMD_CLOSE_SESSION; |
| cmd->session_id = info->session_id; |
| cmd->param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, |
| TEE_PARAM_TYPE_NONE, |
| TEE_PARAM_TYPE_NONE, |
| TEE_PARAM_TYPE_NONE); |
| |
| ret = tzc_invoke_command(0, task_id, TZ_CMD_TEE_SYS, |
| info->param, &origin, NULL, NULL); |
| |
| if (ret) { |
| tz_error("fail to close session, result=0x%08x\n", ret); |
| return ret; |
| } |
| |
| instance_dead = cmd->params[0].value.a; |
| if (instance_dead) { |
| /* destroy instance */ |
| memset(cmd, 0, TEE_COMM_PARAM_BASIC_SIZE); |
| cmd->cmd_id = TAMGR_CMD_DESTROY_INSTANCE; |
| cmd->param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_INPUT, |
| TEE_PARAM_TYPE_NONE, |
| TEE_PARAM_TYPE_NONE, |
| TEE_PARAM_TYPE_NONE); |
| |
| cmd->params[0].value.a = task_id; |
| |
| tzc_invoke_command(0, TZ_TASK_ID_MGR, TZ_CMD_TEE_SYS, |
| info->param, &origin, NULL, NULL); |
| } |
| |
| /* It should clean task client somewhere when open session. |
| * However, this logic isn't there for now. |
| * So we just clean this flag here maunally. |
| */ |
| tc->dead = false; |
| |
| return ret; |
| } |
| |
| static void tzd_release_all_session(void *private_data) |
| { |
| struct tzd_dev_file *temp_dev_file = (struct tzd_dev_file*)private_data; |
| struct tzd_session_info *temp_info; |
| struct tzd_session_info *temp_pos; |
| |
| mutex_lock(&temp_dev_file->tz_mutex); |
| list_for_each_entry_safe(temp_info, temp_pos, &temp_dev_file-> |
| session_list, head) { |
| list_del(&temp_info->head); |
| tzd_release_session(temp_dev_file, temp_info); |
| if (temp_info) |
| kfree(temp_info); |
| } |
| mutex_unlock(&temp_dev_file->tz_mutex); |
| |
| } |
| |
| static int tzd_release(struct inode *inode, struct file *file) |
| { |
| tzd_release_all_session(file->private_data); |
| tzd_resource_free(file->private_data); |
| tz_debug("tzd_release"); |
| return 0; |
| } |
| |
| static int kernel_tzd_open(void) |
| { |
| struct tzd_dev_file *new_dev; |
| |
| tz_debug("kernel_tzd_open"); |
| |
| new_dev = kzalloc(sizeof(*new_dev), GFP_KERNEL); |
| if (!new_dev) { |
| tz_error("kzalloc failed for new dev file allocation"); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&new_dev->dev_shm_head.shm_list); |
| |
| mutex_init(&new_dev->tz_mutex); |
| |
| INIT_LIST_HEAD(&new_dev->session_list); |
| |
| BUG_ON(kernel_dev_file != NULL); |
| kernel_dev_file = new_dev; |
| |
| return 0; |
| } |
| |
| static int kernel_tzd_release(void) |
| { |
| tzd_release_all_session(kernel_dev_file); |
| tzd_resource_free(kernel_dev_file); |
| kernel_dev_file = NULL; |
| tz_debug("kernel_tzd_release"); |
| return 0; |
| } |
| |
| static const struct file_operations tzd_fops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = tzd_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = tzd_compat_ioctl, |
| #endif |
| .open = tzd_open, |
| .mmap = tzd_mmap, |
| .release = tzd_release |
| }; |
| |
| static struct class *driver_class; |
| static dev_t tzd_device_num; |
| static struct cdev tzd_cdev; |
| |
| static int tzd_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct device *class_dev; |
| |
| ret = tzd_shm_init(); |
| if (ret < 0) |
| return ret; |
| |
| /* init task client */ |
| tz_nw_task_client_init_all(); |
| |
| REE_RuntimeInit(); |
| |
| ret = alloc_chrdev_region(&tzd_device_num, 0, 1, |
| TZ_CLIENT_DEVICE_NAME); |
| if (ret < 0) { |
| tz_error("alloc_chrdev_region failed %d", ret); |
| goto shm_exit; |
| } |
| |
| driver_class = class_create(THIS_MODULE, TZ_CLIENT_DEVICE_NAME); |
| if (IS_ERR(driver_class)) { |
| ret = -ENOMEM; |
| tz_error("class_create failed %d", ret); |
| goto unregister_chrdev_region; |
| } |
| |
| class_dev = device_create(driver_class, NULL, tzd_device_num, |
| NULL, TZ_CLIENT_DEVICE_NAME); |
| if (!class_dev) { |
| tz_error("class_device_create failed %d", ret); |
| ret = -ENOMEM; |
| goto class_destroy; |
| } |
| |
| cdev_init(&tzd_cdev, &tzd_fops); |
| tzd_cdev.owner = THIS_MODULE; |
| |
| ret = cdev_add(&tzd_cdev, |
| MKDEV(MAJOR(tzd_device_num), 0), 1); |
| if (ret < 0) { |
| tz_error("cdev_add failed %d", ret); |
| goto class_device_destroy; |
| } |
| |
| ret = kernel_tzd_open(); |
| if (ret < 0) { |
| tz_error("kernel_tzd_open failed\n"); |
| goto cdev_destroy; |
| } |
| |
| tzd_hwrng_task = kthread_run(tzd_hwrng_kthread, NULL, "tz_hwrng"); |
| if (IS_ERR(tzd_hwrng_task)) { |
| tz_error("tzd_hwrng_kthread start failed"); |
| ret = PTR_ERR(tzd_hwrng_task); |
| goto tzd_release; |
| } |
| |
| tzlogger_init(); |
| |
| return ret; |
| |
| tzd_release: |
| kernel_tzd_release(); |
| cdev_destroy: |
| cdev_del(&tzd_cdev); |
| class_device_destroy: |
| device_destroy(driver_class, tzd_device_num); |
| class_destroy: |
| class_destroy(driver_class); |
| unregister_chrdev_region: |
| unregister_chrdev_region(tzd_device_num, 1); |
| shm_exit: |
| tzd_shm_exit(); |
| return ret; |
| } |
| |
| static int tzd_remove(struct platform_device *pdev) |
| { |
| tz_debug("tz driver exit\n"); |
| kthread_stop(tzd_hwrng_task); |
| tzd_shm_exit(); |
| kernel_tzd_release(); |
| cdev_del(&tzd_cdev); |
| device_destroy(driver_class, tzd_device_num); |
| class_destroy(driver_class); |
| unregister_chrdev_region(tzd_device_num, 1); |
| |
| tzlogger_exit(); |
| |
| return 0; |
| } |
| |
| static struct platform_driver tzd_driver = { |
| .probe = tzd_probe, |
| .remove = tzd_remove, |
| .driver = { |
| .name = "berlin-tzd", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| module_platform_driver(tzd_driver); |
| |
| static int __init tzd_platdev_init(void) |
| { |
| return PTR_ERR_OR_ZERO(platform_device_register_simple("berlin-tzd", |
| -1, NULL, 0)); |
| } |
| device_initcall_sync(tzd_platdev_init); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_AUTHOR("Marvell"); |
| MODULE_DESCRIPTION("Marvell TrustZone Driver"); |
| MODULE_VERSION("1.00"); |