646 lines
15 KiB
C
646 lines
15 KiB
C
/*
|
|
* 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);
|