lineage_kernel_xcoverpro/drivers/misc/tzdev/tz_msm_platform.c

649 lines
15 KiB
C
Executable File

/*
* 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/clk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/hrtimer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#if defined(CONFIG_ARCH_MSM) && defined(CONFIG_MSM_SCM)
#if defined(CONFIG_ARCH_MSM8939) || defined(CONFIG_ARCH_MSM8996)
#include <asm/cacheflush.h>
#include <soc/qcom/scm.h>
#include <soc/qcom/qseecomi.h>
#else
#error "Unsupported target! The only MSM8996 and MSM8939 are supported for Qualcomm chipset"
#endif
#endif
#include "tzdev.h"
#include "tzlog.h"
#include "tz_iwlog.h"
#include "tz_platform.h"
#include "tz_kthread_pool.h"
/* Define a tzdev device structure for use with dev_debug() etc */
static struct device_driver tzdev_drv = {
.name = "tzdev"
};
static struct device tzd = {
.driver = &tzdev_drv
};
static struct device *tzdev_dev = &tzd;
#define QSEE_CE_CLK_100MHZ 100000000
#define QSEE_CLK_ON 0x1
#define QSEE_CLK_OFF 0x0
/* svc_id and cmd_id to call QSEE 3-rd party smc handler */
#define TZ_SVC_EXECUTIVE_EXT 250
#define TZ_CMD_ID_EXEC_SMC_EXT 1
#define SCM_V2_EBUSY -12
#define SCM_TZM_FNID(s, c) (((((s) & 0xFF) << 8) | ((c) & 0xFF)) | 0x33000000)
#define TZ_EXECUTIVE_EXT_ID_PARAM_ID \
TZ_SYSCALL_CREATE_PARAM_ID_4( \
TZ_SYSCALL_PARAM_TYPE_BUF_RW, TZ_SYSCALL_PARAM_TYPE_VAL,\
TZ_SYSCALL_PARAM_TYPE_BUF_RW, TZ_SYSCALL_PARAM_TYPE_VAL)
static struct workqueue_struct *tzdev_msm_workq;
struct tzdev_msm_work {
struct work_struct work;
struct completion done;
int ret;
union {
unsigned long val;
void *ptr;
} data;
};
struct tzdev_msm_msg {
uint32_t p0;
uint32_t p1;
uint32_t p2;
uint32_t p3;
uint32_t p4;
uint32_t p5;
uint32_t p6;
__s64 tv_sec;
__s32 tv_nsec;
uint32_t crypto_clk;
};
struct tzdev_msm_ret_msg {
uint32_t p0;
uint32_t p1;
uint32_t p2;
uint32_t p3;
uint32_t timer_remains_ms;
};
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT)
static int tzdev_qc_pm_clock_enable(void);
static void tzdev_qc_pm_clock_disable(void);
static struct clk *tzdev_core_src = NULL;
static struct clk *tzdev_core_clk = NULL;
static struct clk *tzdev_iface_clk = NULL;
static struct clk *tzdev_bus_clk = NULL;
static DEFINE_MUTEX(tzdev_qc_clk_mutex);
static int tzdev_qc_clk_users = 0;
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT */
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG)
static unsigned long tzdev_qc_clk = QSEE_CLK_OFF;
#else /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG */
static unsigned long tzdev_qc_clk = QSEE_CLK_ON;
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG */
static DEFINE_MUTEX(tzdev_init_seq_mutex);
static ssize_t tzdev_run_init_sequence_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
ssize_t ret;
(void) dev;
(void) attr;
(void) buf;
mutex_lock(&tzdev_init_seq_mutex);
ret = tzdev_run_init_sequence();
mutex_unlock(&tzdev_init_seq_mutex);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR(init_seq, S_IWUSR | S_IWGRP, NULL, tzdev_run_init_sequence_store);
#define BF_SMC_SUCCESS 0
#define BF_SMC_INTERRUPTED 1
#define BF_SMC_KERNEL_PANIC 2
DEFINE_MUTEX(tzdev_smc_lock);
struct hrtimer tzdev_get_event_timer;
static enum hrtimer_restart tzdev_get_event_timer_handler(struct hrtimer *timer)
{
tz_kthread_pool_cmd_send();
return HRTIMER_NORESTART;
}
static int tzdev_scm_call(struct tzdev_smc_data *data)
{
struct tzdev_msm_msg msm_msg = {
data->args[0], data->args[1], data->args[2], data->args[3], data->args[4],
data->args[5], data->args[6], 0, 0, tzdev_qc_clk
};
struct tzdev_msm_ret_msg ret_msg = {0, 0, 0, 0, 0};
struct scm_desc desc = {0};
struct timespec ts;
void *scm_buf;
int ret;
BUG_ON(raw_smp_processor_id() != 0);
scm_buf = kzalloc(PAGE_ALIGN(sizeof(msm_msg)), GFP_KERNEL);
if (!scm_buf)
return -ENOMEM;
getnstimeofday(&ts);
msm_msg.tv_sec = ts.tv_sec;
msm_msg.tv_nsec = ts.tv_nsec;
memcpy(scm_buf, &msm_msg, sizeof(msm_msg));
dmac_flush_range(scm_buf, (unsigned char *)scm_buf + sizeof(msm_msg));
desc.arginfo = TZ_EXECUTIVE_EXT_ID_PARAM_ID;
desc.args[0] = virt_to_phys(scm_buf);
desc.args[1] = sizeof(msm_msg);
desc.args[2] = virt_to_phys(scm_buf);
desc.args[3] = sizeof(ret_msg);
mutex_lock(&tzdev_smc_lock);
hrtimer_cancel(&tzdev_get_event_timer);
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT) && !defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG)
ret = tzdev_qc_pm_clock_enable();
if (ret)
goto out;
#endif
do {
ret = scm_call2(SCM_TZM_FNID(TZ_SVC_EXECUTIVE_EXT,
TZ_CMD_ID_EXEC_SMC_EXT), &desc);
} while (!ret && desc.ret[0] == BF_SMC_INTERRUPTED);
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT) && !defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG)
tzdev_qc_pm_clock_disable();
#endif
if (ret) {
tzdev_print(0, "scm_call() failed: %d\n", ret);
if (ret == SCM_V2_EBUSY)
ret = -EBUSY;
goto out;
}
if (desc.ret[0] == BF_SMC_KERNEL_PANIC) {
static unsigned int panic_msg_printed;
tz_iwlog_read_buffers();
if (!panic_msg_printed) {
tzdev_print(0, "Secure kernel panicked\n");
panic_msg_printed = 1;
}
#if defined(CONFIG_TZDEV_SWD_PANIC_IS_CRITICAL)
panic("Secure kernel panicked\n");
#else
ret = -EIO;
goto out;
#endif
}
dmac_flush_range(scm_buf, (unsigned char *)scm_buf + sizeof(ret_msg));
memcpy(&ret_msg, scm_buf, sizeof(ret_msg));
if (ret_msg.timer_remains_ms) {
unsigned long secs;
unsigned long nsecs;
secs = ret_msg.timer_remains_ms / MSEC_PER_SEC;
nsecs = (ret_msg.timer_remains_ms % MSEC_PER_SEC) * NSEC_PER_MSEC;
hrtimer_start(&tzdev_get_event_timer,
ktime_set(secs, nsecs), HRTIMER_MODE_REL);
}
out:
mutex_unlock(&tzdev_smc_lock);
kfree(scm_buf);
data->args[0] = ret_msg.p0;
data->args[1] = ret_msg.p1;
data->args[2] = ret_msg.p2;
data->args[3] = ret_msg.p3;
return ret;
}
static void tzdev_smc_call_routed(struct work_struct *work)
{
struct tzdev_msm_work *msm_work = container_of(work, struct tzdev_msm_work, work);
msm_work->ret = tzdev_scm_call(msm_work->data.ptr);
complete(&msm_work->done);
}
int tzdev_platform_smc_call(struct tzdev_smc_data *data)
{
struct tzdev_msm_work msm_work;
/* If current smc request is schedulable one then it's guaranteed
* that request done via kernel worker thread pinned to cpu 0
* and such request could be invoked directly.
* Otherwise it must be routed to cpu 0.
*/
if (likely(data->args[0] == TZDEV_SMC_SCHEDULE))
return tzdev_scm_call(data);
INIT_WORK_ONSTACK(&msm_work.work, tzdev_smc_call_routed);
init_completion(&msm_work.done);
msm_work.data.ptr = data;
BUG_ON(!queue_work_on(0, tzdev_msm_workq, &msm_work.work));
wait_for_completion(&msm_work.done);
return msm_work.ret;
}
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT)
static int tzdev_qc_pm_clock_initialize(void)
{
int ret = 0;
int freq_val = 0;
#if defined(CONFIG_ARCH_MSM8939)
const char *prop_name = "qcom,freq-val";
#else /* CONFIG_ARCH_MSM8939 */
const char *prop_name = "qcom,ce-opp-freq";
#endif /* CONFIG_ARCH_MSM8939 */
tzdev_core_src = clk_get(tzdev_dev, "core_clk_src");
if (IS_ERR(tzdev_core_src)) {
tzdev_print(0, "no tzdev_core_src, ret = %d\n", ret);
ret = PTR_ERR(tzdev_core_src);
goto error;
}
if (of_property_read_u32(tzdev_dev->of_node, prop_name, &freq_val)) {
freq_val = QSEE_CE_CLK_100MHZ;
tzdev_print(0, "Unable to get frequency value from \"%s\" property. " \
"Set default: %d\n", prop_name, freq_val);
}
ret = clk_set_rate(tzdev_core_src, freq_val);
if (ret) {
tzdev_print(0, "clk_set_rate failed, ret = %d\n", ret);
ret = -EIO;
goto put_core_src_clk;
}
tzdev_core_clk = clk_get(tzdev_dev, "core_clk");
if (IS_ERR(tzdev_core_clk)) {
tzdev_print(0, "no tzdev_core_clk\n");
ret = PTR_ERR(tzdev_core_clk);
goto clear_core_clk;
}
tzdev_iface_clk = clk_get(tzdev_dev, "iface_clk");
if (IS_ERR(tzdev_iface_clk)) {
tzdev_print(0, "no tzdev_iface_clk\n");
ret = PTR_ERR(tzdev_iface_clk);
goto put_core_clk;
}
tzdev_bus_clk = clk_get(tzdev_dev, "bus_clk");
if (IS_ERR(tzdev_bus_clk)) {
tzdev_print(0, "no tzdev_bus_clk\n");
ret = PTR_ERR(tzdev_bus_clk);
goto put_iface_clk;
}
tzdev_print(0, "Got QC HW crypto clks\n");
return ret;
put_iface_clk:
clk_put(tzdev_iface_clk);
tzdev_bus_clk = NULL;
put_core_clk:
clk_put(tzdev_core_clk);
tzdev_iface_clk = NULL;
clear_core_clk:
tzdev_core_clk = NULL;
put_core_src_clk:
clk_put(tzdev_core_src);
error:
tzdev_core_src = NULL;
return ret;
}
static void tzdev_qc_pm_clock_finalize(void)
{
clk_put(tzdev_bus_clk);
clk_put(tzdev_iface_clk);
clk_put(tzdev_core_clk);
clk_put(tzdev_core_src);
}
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT */
static int tzdev_qc_probe(struct platform_device *pdev)
{
tzdev_dev->of_node = pdev->dev.of_node;
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT)
tzdev_qc_pm_clock_initialize();
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT */
hrtimer_init(&tzdev_get_event_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
tzdev_get_event_timer.function = tzdev_get_event_timer_handler;
return 0;
}
static int tzdev_qc_remove(struct platform_device *pdev)
{
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT)
tzdev_qc_pm_clock_finalize();
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT */
hrtimer_cancel(&tzdev_get_event_timer);
return 0;
}
static int tzdev_qc_suspend(struct platform_device *pdev, pm_message_t state)
{
return 0;
}
static int tzdev_qc_resume(struct platform_device *pdev)
{
return 0;
}
struct of_device_id tzdev_qc_match[] = {
{
.compatible = "qcom,tzd",
},
{}
};
struct platform_driver tzdev_qc_plat_driver = {
.probe = tzdev_qc_probe,
.remove = tzdev_qc_remove,
.suspend = tzdev_qc_suspend,
.resume = tzdev_qc_resume,
.driver = {
.name = "tzdev",
.owner = THIS_MODULE,
.of_match_table = tzdev_qc_match,
},
};
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT)
static int tzdev_qc_pm_clock_enable(void)
{
int ret;
mutex_lock(&tzdev_qc_clk_mutex);
ret = clk_prepare_enable(tzdev_core_clk);
if (ret) {
tzdev_print(0, "failed to enable core clk, ret=%d\n", ret);
goto out;
}
ret = clk_prepare_enable(tzdev_iface_clk);
if (ret) {
tzdev_print(0, "failed to enable iface clk, ret=%d\n", ret);
goto unprepare_core_clk;
}
ret = clk_prepare_enable(tzdev_bus_clk);
if (ret) {
tzdev_print(0, "failed to enable bus clk, ret=%d\n\n", ret);
goto unprepare_iface_clk;
}
tzdev_qc_clk_users++;
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG)
if (tzdev_qc_clk_users == 1)
tzdev_qc_clk = QSEE_CLK_ON;
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG */
goto out;
unprepare_iface_clk:
clk_disable_unprepare(tzdev_iface_clk);
unprepare_core_clk:
clk_disable_unprepare(tzdev_core_clk);
out:
mutex_unlock(&tzdev_qc_clk_mutex);
return ret;
}
static void tzdev_qc_pm_clock_disable(void)
{
mutex_lock(&tzdev_qc_clk_mutex);
tzdev_qc_clk_users--;
BUG_ON(tzdev_qc_clk_users < 0);
clk_disable_unprepare(tzdev_iface_clk);
clk_disable_unprepare(tzdev_core_clk);
clk_disable_unprepare(tzdev_bus_clk);
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG)
if (!tzdev_qc_clk_users)
tzdev_qc_clk = QSEE_CLK_OFF;
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG */
mutex_unlock(&tzdev_qc_clk_mutex);
}
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_MANAGEMENT */
int tzdev_platform_register(void)
{
return platform_driver_register(&tzdev_qc_plat_driver);
}
void tzdev_platform_unregister(void)
{
platform_driver_unregister(&tzdev_qc_plat_driver);
}
int tzdev_platform_init(void)
{
struct workqueue_struct *workq;
struct workqueue_attrs *attrs;
struct tz_cdev *tzdev_cdev;
int ret;
tzdev_cdev = tzdev_get_cdev();
ret = device_create_file(tzdev_cdev->device, &dev_attr_init_seq);
if (ret) {
tzdev_print(0, "device_create_file() failed, error=%d\n", ret);
return ret;
}
workq = create_singlethread_workqueue("tzdev_msm_workq");
if (!workq) {
tzdev_print(0, "create_singlethread_workqueue() failed\n");
ret = -ENOMEM;
goto out_free_sysfs;
}
attrs = alloc_workqueue_attrs(GFP_KERNEL);
if (!attrs) {
tzdev_print(0, "alloc_workqueue_attrs() failed\n");
ret = -ENOMEM;
goto out_free_workq;
}
attrs->nice = 0;
attrs->no_numa = true;
/* Bind workqueue to cpu 0 */
cpumask_clear(attrs->cpumask);
cpumask_set_cpu(0, attrs->cpumask);
ret = apply_workqueue_attrs(workq, attrs);
if (ret) {
tzdev_print(0, "apply_workqueue_attrs() failed, ret=%d\n", ret);
goto out_free_attrs;
}
free_workqueue_attrs(attrs);
tzdev_msm_workq = workq;
return 0;
out_free_attrs:
free_workqueue_attrs(attrs);
out_free_workq:
destroy_workqueue(workq);
out_free_sysfs:
device_remove_file(tzdev_cdev->device, &dev_attr_init_seq);
return ret;
}
int tzdev_platform_fini(void)
{
if (tzdev_msm_workq) {
flush_workqueue(tzdev_msm_workq);
destroy_workqueue(tzdev_msm_workq);
tzdev_msm_workq = NULL;
}
return 0;
}
int tzdev_platform_open(struct inode *inode, struct file *filp)
{
(void)inode;
(void)filp;
return tzdev_run_init_sequence();
}
#if defined(CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG)
static int tzdev_set_crypto_clk(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_CRYPTO_CLOCK_ON:
if (!data->crypto_clk_state) {
ret = tzdev_qc_pm_clock_enable();
if (!ret)
data->crypto_clk_state = 1;
} else {
tzdev_print(0, "Trying to enable crypto clocks twice, filp=%pK\n", filp);
ret = -EBUSY;
}
break;
case TZIO_CRYPTO_CLOCK_OFF:
if (data->crypto_clk_state) {
tzdev_qc_pm_clock_disable();
data->crypto_clk_state = 0;
} else {
tzdev_print(0, "Trying to disable crypto clocks twice, filp=%pK\n", filp);
ret = -EBUSY;
}
break;
default:
tzdev_print(0, "Unknown crypto clocks request, state=%u filp=%pK\n", state, filp);
ret = -EINVAL;
break;
}
mutex_unlock(&data->mutex);
return ret;
}
long tzdev_fd_platform_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case TZIO_CRYPTO_CLOCK_CONTROL:
return tzdev_set_crypto_clk(filp, arg);
default:
return -ENOTTY;
}
}
int tzdev_fd_platform_close(struct inode *inode, struct file *filp)
{
struct tzdev_fd_data *data = filp->private_data;
(void)inode;
if (data->crypto_clk_state)
tzdev_set_crypto_clk(filp, TZIO_CRYPTO_CLOCK_OFF);
return 0;
}
uint32_t tzdev_platform_get_sysconf_flags(void)
{
uint32_t flags = 0;
flags |= SYSCONF_NWD_CRYPTO_CLOCK_MANAGEMENT;
return flags;
}
#endif /* CONFIG_TZDEV_QC_CRYPTO_CLOCKS_USR_MNG */