| /* |
| * Copyright (C) 2012-2018, Samsung Electronics Co., Ltd. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| */ |
| |
| #include <linux/atomic.h> |
| #include <linux/buffer_head.h> |
| #include <linux/completion.h> |
| #include <linux/cpu.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioctl.h> |
| #include <linux/kernel.h> |
| #include <linux/list.h> |
| #include <linux/miscdevice.h> |
| #include <linux/mmzone.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/pid.h> |
| #include <linux/platform_device.h> |
| #include <linux/reboot.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/syscore_ops.h> |
| #include <linux/time.h> |
| #include <linux/uaccess.h> |
| #include <linux/workqueue.h> |
| |
| #include <asm/segment.h> |
| #include <asm/uaccess.h> |
| |
| #include "sysdep.h" |
| #include "tz_boost.h" |
| #include "tz_cdev.h" |
| #include "tz_cma.h" |
| #include "tz_deploy_tzar.h" |
| #include "tz_fsdev.h" |
| #include "tz_iw_boot_log.h" |
| #include "tz_iwio.h" |
| #include "tz_iwlog.h" |
| #include "tz_iwservice.h" |
| #include "tz_iwsock.h" |
| #include "tz_kthread_pool.h" |
| #include "tz_mem.h" |
| #include "tz_panic_dump.h" |
| #include "tz_platform.h" |
| #include "tz_uiwsock.h" |
| #include "tzdev.h" |
| #include "tzlog.h" |
| #include "tzprofiler.h" |
| |
| MODULE_AUTHOR("Jaemin Ryu <jm77.ryu@samsung.com>"); |
| MODULE_AUTHOR("Vasily Leonenko <v.leonenko@samsung.com>"); |
| MODULE_AUTHOR("Alex Matveev <alex.matveev@samsung.com>"); |
| MODULE_DESCRIPTION("TZDEV driver"); |
| MODULE_LICENSE("GPL"); |
| |
| unsigned int tzdev_verbosity; |
| unsigned int tzdev_teec_verbosity; |
| unsigned int tzdev_kthread_verbosity; |
| unsigned int tzdev_uiwsock_verbosity; |
| |
| module_param(tzdev_verbosity, uint, 0644); |
| MODULE_PARM_DESC(tzdev_verbosity, "0: normal, 1: verbose, 2: debug"); |
| |
| module_param(tzdev_teec_verbosity, uint, 0644); |
| MODULE_PARM_DESC(tzdev_teec_verbosity, "0: error, 1: info, 2: debug"); |
| |
| module_param(tzdev_kthread_verbosity, uint, 0644); |
| MODULE_PARM_DESC(tzdev_kthread_verbosity, "0: error, 1: info, 2: debug"); |
| |
| module_param(tzdev_uiwsock_verbosity, uint, 0644); |
| MODULE_PARM_DESC(tzdev_uiwsock_verbosity, "0: error, 1: info, 2: debug"); |
| |
| enum tzdev_nwd_state { |
| TZDEV_NWD_DOWN, |
| TZDEV_NWD_UP, |
| }; |
| |
| enum tzdev_swd_state { |
| TZDEV_SWD_DOWN, |
| TZDEV_SWD_UP, |
| TZDEV_SWD_DEAD |
| }; |
| |
| struct tzdev_shmem { |
| struct list_head link; |
| unsigned int id; |
| }; |
| |
| static atomic_t tzdev_nwd_state = ATOMIC_INIT(TZDEV_NWD_DOWN); |
| static atomic_t tzdev_swd_state = ATOMIC_INIT(TZDEV_SWD_DOWN); |
| |
| static struct tzio_sysconf tz_sysconf; |
| |
| static int tzdev_alloc_aux_channels(void) |
| { |
| int ret; |
| unsigned int i; |
| |
| for (i = 0; i < NR_SW_CPU_IDS; ++i) { |
| ret = tz_iwio_alloc_aux_channel(i); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int tzdev_sysconf(struct tzio_sysconf *sysconf) |
| { |
| int ret = 0; |
| struct tz_iwio_aux_channel *ch; |
| |
| ch = tz_iwio_get_aux_channel(); |
| |
| sysconf->nwd_sysconf.flags = tzdev_platform_get_sysconf_flags(); |
| |
| #ifdef CONFIG_TZDEV_HOTPLUG |
| sysconf->nwd_sysconf.flags |= SYSCONF_NWD_CPU_HOTPLUG; |
| #endif /* CONFIG_TZDEV_HOTPLUG */ |
| |
| #ifdef CONFIG_TZDEV_DEPLOY_TZAR |
| sysconf->nwd_sysconf.flags |= SYSCONF_NWD_TZDEV_DEPLOY_TZAR; |
| #endif /* CONFIG_TZDEV_DEPLOY_TZAR */ |
| |
| memcpy(ch->buffer, &sysconf->nwd_sysconf, sizeof(struct tzio_nwd_sysconf)); |
| |
| ret = tzdev_smc_sysconf(); |
| if (ret) { |
| tzdev_print(0, "tzdev_smc_sysconf() failed with %d\n", ret); |
| goto out; |
| } |
| |
| memcpy(&sysconf->swd_sysconf, ch->buffer, sizeof(struct tzio_swd_sysconf)); |
| out: |
| tz_iwio_put_aux_channel(); |
| |
| return ret; |
| } |
| |
| static irqreturn_t tzdev_event_handler(int irq, void *ptr) |
| { |
| tz_kthread_pool_cmd_send(); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #if CONFIG_TZDEV_IWI_PANIC != 0 |
| static void dump_kernel_panic_bh(struct work_struct *work) |
| { |
| atomic_set(&tzdev_swd_state, TZDEV_SWD_DEAD); |
| if (atomic_read(&tzdev_nwd_state) == TZDEV_NWD_UP) { |
| tz_iw_boot_log_read(); |
| tz_iwlog_read_buffers(); |
| tz_iwsock_kernel_panic_handler(); |
| tz_kthread_pool_fini(); |
| tzdev_mem_release_panic_handler(); |
| panic("tzdev: IWI_PANIC raised\n"); |
| } |
| } |
| |
| static DECLARE_WORK(dump_kernel_panic, dump_kernel_panic_bh); |
| |
| static irqreturn_t tzdev_panic_handler(int irq, void *ptr) |
| { |
| schedule_work(&dump_kernel_panic); |
| return IRQ_HANDLED; |
| } |
| #endif |
| |
| static void tzdev_resolve_iwis_id(unsigned int *iwi_event, unsigned int *iwi_panic) |
| { |
| struct device_node *node; |
| |
| node = of_find_compatible_node(NULL, NULL, "samsung,blowfish"); |
| if (!node) |
| node = of_find_compatible_node(NULL, NULL, "samsung,teegris"); |
| |
| if (!node) { |
| *iwi_event = CONFIG_TZDEV_IWI_EVENT; |
| #if CONFIG_TZDEV_IWI_PANIC != 0 |
| *iwi_panic = CONFIG_TZDEV_IWI_PANIC; |
| #endif |
| tzdev_print(0, "of_find_compatible_node() failed\n"); |
| return; |
| } |
| |
| *iwi_event = irq_of_parse_and_map(node, 0); |
| if (!*iwi_event) { |
| *iwi_event = CONFIG_TZDEV_IWI_EVENT; |
| tzdev_print(0, "Use IWI event IRQ number from config %d\n", |
| CONFIG_TZDEV_IWI_EVENT); |
| } |
| |
| #if CONFIG_TZDEV_IWI_PANIC != 0 |
| *iwi_panic = irq_of_parse_and_map(node, 1); |
| if (!*iwi_panic) { |
| *iwi_panic = CONFIG_TZDEV_IWI_PANIC; |
| tzdev_print(0, "Use IWI panic IRQ number from config %d\n", |
| CONFIG_TZDEV_IWI_PANIC); |
| } |
| #endif |
| of_node_put(node); |
| } |
| |
| static void tzdev_register_iwis(void) |
| { |
| int ret; |
| unsigned int iwi_event; |
| unsigned int iwi_panic; |
| |
| tzdev_resolve_iwis_id(&iwi_event, &iwi_panic); |
| |
| ret = request_irq(iwi_event, tzdev_event_handler, 0, "tzdev_iwi_event", NULL); |
| if (ret) |
| tzdev_print(0, "TZDEV_IWI_EVENT registration failed: %d\n", ret); |
| |
| #if CONFIG_TZDEV_IWI_PANIC != 0 |
| ret = request_irq(iwi_panic, tzdev_panic_handler, 0, "tzdev_iwi_panic", NULL); |
| if (ret) |
| tzdev_print(0, "TZDEV_IWI_PANIC registration failed: %d\n", ret); |
| #endif |
| } |
| |
| int __tzdev_smc_cmd(struct tzdev_smc_data *data) |
| { |
| int ret; |
| |
| tzprofiler_enter_sw(); |
| tz_iwlog_schedule_delayed_work(); |
| |
| tzdev_print(2, "tzdev_smc_cmd: enter: args={0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x}\n", |
| data->args[0], data->args[1], data->args[2], data->args[3], |
| data->args[4], data->args[5], data->args[6]); |
| |
| ret = tzdev_platform_smc_call(data); |
| |
| tzdev_print(2, "tzdev_smc_cmd: exit: args={0x%x 0x%x 0x%x 0x%x} ret=%d\n", |
| data->args[0], data->args[1], data->args[2], data->args[3], ret); |
| |
| tz_iwlog_cancel_delayed_work(); |
| tz_iwlog_read_buffers(); |
| |
| tzprofiler_wait_for_bufs(); |
| tzprofiler_exit_sw(); |
| |
| tz_iwsock_check_notifications(); |
| tz_iwservice_handle_swd_request(); |
| |
| return ret; |
| } |
| |
| /* TZDEV interface functions */ |
| unsigned int tzdev_is_up(void) |
| { |
| return atomic_read(&tzdev_swd_state) == TZDEV_SWD_UP; |
| } |
| |
| int tzdev_run_init_sequence(void) |
| { |
| int ret = 0; |
| |
| if (atomic_read(&tzdev_swd_state) == TZDEV_SWD_DOWN) { |
| /* check kernel and driver version compatibility with TEEGRIS */ |
| ret = tzdev_smc_check_version(); |
| if (ret == -ENOSYS || ret == -EINVAL) { |
| /* version is not compatibile. Not critical, continue ... */ |
| ret = 0; |
| } else if (ret) { |
| tzdev_print(0, "tzdev_smc_check_version() failed\n"); |
| goto out; |
| } |
| |
| if (tzdev_cma_mem_register()) { |
| tzdev_print(0, "tzdev_cma_mem_register() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tzdev_alloc_aux_channels()) { |
| tzdev_print(0, "tzdev_alloc_AUX_Channels() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tz_iwservice_alloc()) { |
| tzdev_print(0, "tzdev_alloc_service_channel() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tzdev_mem_init()) { |
| tzdev_print(0, "tzdev_mem_init() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tz_iwlog_initialize()) { |
| tzdev_print(0, "tz_iwlog_initialize() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tzprofiler_initialize()) { |
| tzdev_print(0, "tzprofiler_initialize() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tz_panic_dump_alloc_buffer()) { |
| tzdev_print(0, "tz_panic_dump_alloc_buffer() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tzdev_sysconf(&tz_sysconf)) { |
| tzdev_print(0, "tzdev_sysconf() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tz_iwsock_init()) { |
| tzdev_print(0, "tz_iwsock_init failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| tzdev_register_iwis(); |
| tz_boost_set_boost_mask(tz_sysconf.swd_sysconf.big_cpus_mask); |
| |
| if (tz_fsdev_initialize()) { |
| tzdev_print(0, "tz_fsdev_initialize() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (atomic_cmpxchg(&tzdev_swd_state, TZDEV_SWD_DOWN, TZDEV_SWD_UP)) { |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| |
| if (tzdev_deploy_tzar()) { |
| tzdev_print(0, "tzdev_deploy_tzar() failed\n"); |
| ret = -ESHUTDOWN; |
| goto out; |
| } |
| } |
| out: |
| if (ret == -ESHUTDOWN) { |
| atomic_set(&tzdev_swd_state, TZDEV_SWD_DEAD); |
| tz_iw_boot_log_read(); |
| tz_iwlog_read_buffers(); |
| } |
| |
| return ret; |
| } |
| |
| static int tzdev_get_sysconf(struct file *filp, unsigned long arg) |
| { |
| struct tzio_sysconf __user *argp = (struct tzio_sysconf __user *)arg; |
| |
| if (copy_to_user(argp, &tz_sysconf, sizeof(struct tzio_sysconf))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| static int tzdev_register_shared_memory(struct file *filp, unsigned long arg) |
| { |
| int ret; |
| struct tzdev_shmem *shmem; |
| struct tzdev_fd_data *data = filp->private_data; |
| struct tzio_mem_register __user *argp = (struct tzio_mem_register __user *)arg; |
| struct tzio_mem_register s; |
| |
| if (copy_from_user(&s, argp, sizeof(struct tzio_mem_register))) |
| return -EFAULT; |
| |
| shmem = kzalloc(sizeof(struct tzdev_shmem), GFP_KERNEL); |
| if (!shmem) { |
| tzdev_print(0, "Failed to allocate shmem structure\n"); |
| return -ENOMEM; |
| } |
| |
| ret = tzdev_mem_register_user(UINT_PTR(s.ptr), s.size, s.write); |
| if (ret < 0) { |
| kfree(shmem); |
| return ret; |
| } |
| |
| INIT_LIST_HEAD(&shmem->link); |
| shmem->id = ret; |
| |
| spin_lock(&data->shmem_list_lock); |
| list_add(&shmem->link, &data->shmem_list); |
| spin_unlock(&data->shmem_list_lock); |
| |
| return shmem->id; |
| } |
| |
| static int tzdev_release_shared_memory(struct file *filp, unsigned int id) |
| { |
| struct tzdev_shmem *shmem; |
| struct tzdev_fd_data *data = filp->private_data; |
| unsigned int found = 0; |
| |
| spin_lock(&data->shmem_list_lock); |
| list_for_each_entry(shmem, &data->shmem_list, link) { |
| if (shmem->id == id) { |
| list_del(&shmem->link); |
| found = 1; |
| break; |
| } |
| } |
| spin_unlock(&data->shmem_list_lock); |
| |
| if (!found) |
| return -EINVAL; |
| |
| kfree(shmem); |
| |
| return tzdev_mem_release_user(id); |
| } |
| |
| static int tzdev_boost_control(struct file *filp, unsigned int state) |
| { |
| struct tzdev_fd_data *data = filp->private_data; |
| int ret = 0; |
| |
| mutex_lock(&data->mutex); |
| |
| switch (state) { |
| case TZIO_BOOST_ON: |
| if (!data->boost_state) { |
| tz_boost_enable(); |
| data->boost_state = 1; |
| } else { |
| tzdev_print(0, "Trying to enable boost twice, filp=%pK\n", filp); |
| ret = -EBUSY; |
| } |
| break; |
| case TZIO_BOOST_OFF: |
| if (data->boost_state) { |
| tz_boost_disable(); |
| data->boost_state = 0; |
| } else { |
| tzdev_print(0, "Trying to disable boost twice, filp=%pK\n", filp); |
| ret = -EBUSY; |
| } |
| break; |
| default: |
| tzdev_print(0, "Unknown boost request, state=%u filp=%pK\n", state, filp); |
| ret = -EINVAL; |
| break; |
| } |
| |
| mutex_unlock(&data->mutex); |
| |
| return ret; |
| } |
| |
| static int tzdev_open(struct inode *inode, struct file *filp) |
| { |
| struct tzdev_fd_data *data; |
| |
| if (!tzdev_is_up()) |
| return -EPERM; |
| |
| data = kzalloc(sizeof(struct tzdev_fd_data), GFP_KERNEL); |
| if (!data) { |
| tzdev_print(0, "Failed to allocate private data\n"); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&data->shmem_list); |
| spin_lock_init(&data->shmem_list_lock); |
| mutex_init(&data->mutex); |
| |
| filp->private_data = data; |
| |
| return 0; |
| } |
| |
| static int tzdev_release(struct inode *inode, struct file *filp) |
| { |
| struct tzdev_shmem *shmem, *tmp; |
| struct tzdev_fd_data *data = filp->private_data; |
| |
| list_for_each_entry_safe(shmem, tmp, &data->shmem_list, link) { |
| list_del(&shmem->link); |
| tzdev_mem_release_user(shmem->id); |
| kfree(shmem); |
| } |
| |
| if (data->boost_state) |
| tzdev_boost_control(filp, TZIO_BOOST_OFF); |
| |
| tzdev_fd_platform_close(inode, filp); |
| |
| mutex_destroy(&data->mutex); |
| |
| kfree(data); |
| |
| return 0; |
| } |
| |
| static long tzdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| switch (cmd) { |
| case TZIO_GET_SYSCONF: |
| return tzdev_get_sysconf(filp, arg); |
| case TZIO_MEM_REGISTER: |
| return tzdev_register_shared_memory(filp, arg); |
| case TZIO_MEM_RELEASE: |
| return tzdev_release_shared_memory(filp, arg); |
| case TZIO_BOOST_CONTROL: |
| return tzdev_boost_control(filp, arg); |
| default: |
| return tzdev_fd_platform_ioctl(filp, cmd, arg); |
| } |
| } |
| |
| static const struct file_operations tzdev_fops = { |
| .owner = THIS_MODULE, |
| .open = tzdev_open, |
| .release = tzdev_release, |
| .unlocked_ioctl = tzdev_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = tzdev_ioctl, |
| #endif /* CONFIG_COMPAT */ |
| }; |
| |
| static struct tz_cdev tzdev_cdev = { |
| .name = "tzdev", |
| .fops = &tzdev_fops, |
| .owner = THIS_MODULE, |
| }; |
| |
| struct tz_cdev *tzdev_get_cdev(void) |
| { |
| return &tzdev_cdev; |
| } |
| |
| static int exit_tzdev(struct notifier_block *cb, unsigned long code, void *unused) |
| { |
| (void)cb; |
| (void)code; |
| (void)unused; |
| |
| atomic_set(&tzdev_nwd_state, TZDEV_NWD_DOWN); |
| |
| tzdev_platform_unregister(); |
| tz_cdev_unregister(&tzdev_cdev); |
| tzdev_cma_mem_release(tzdev_cdev.device); |
| tz_kthread_pool_fini(); |
| tz_hotplug_exit(); |
| |
| tz_uiwsock_fini(); |
| tz_iwsock_fini(); |
| |
| tzdev_print(0, "tzdev exit done.\n"); |
| |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block tzdev_reboot_nb = { |
| .notifier_call = exit_tzdev, |
| }; |
| |
| static int __init init_tzdev(void) |
| { |
| int rc; |
| |
| rc = tz_cdev_register(&tzdev_cdev); |
| if (rc) { |
| tzdev_print(0, "tz_cdev_register() for device failed with error=%d\n", rc); |
| return rc; |
| } |
| |
| rc = tz_uiwsock_init(); |
| if (rc) { |
| tzdev_print(0, "tz_uiwsock_init() failed with error=%d\n", rc); |
| goto uiwsock_initialization_failed; |
| } |
| |
| rc = tzdev_platform_register(); |
| if (rc) { |
| tzdev_print(0, "tzdev_platform_register() failed with error=%d\n", rc); |
| goto platform_driver_registration_failed; |
| } |
| |
| rc = tz_hotplug_init(); |
| if (rc) { |
| tzdev_print(0, "tzdev_init_hotplug() failed with error=%d\n", rc); |
| goto hotplug_initialization_failed; |
| } |
| |
| tzdev_cma_mem_init(tzdev_cdev.device); |
| |
| rc = tzdev_platform_init(); |
| if (rc) { |
| tzdev_print(0, "tzdev_platform_init() failed with error=%d\n", rc); |
| goto platform_initialization_failed; |
| } |
| |
| rc = register_reboot_notifier(&tzdev_reboot_nb); |
| if (rc) { |
| tzdev_print(0, "reboot notifier registration failed() failed with error=%d\n", rc); |
| goto reboot_notifier_registration_failed; |
| } |
| |
| atomic_set(&tzdev_nwd_state, TZDEV_NWD_UP); |
| |
| return rc; |
| |
| reboot_notifier_registration_failed: |
| tzdev_platform_fini(); |
| platform_initialization_failed: |
| tzdev_cma_mem_release(tzdev_cdev.device); |
| tz_hotplug_exit(); |
| hotplug_initialization_failed: |
| tzdev_platform_unregister(); |
| platform_driver_registration_failed: |
| tz_uiwsock_fini(); |
| uiwsock_initialization_failed: |
| tz_cdev_unregister(&tzdev_cdev); |
| panic("tzdev: failed to initialize device, rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| module_init(init_tzdev); |