You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
571 lines
14 KiB
571 lines
14 KiB
/* |
|
* Copyright (C) 2013 Google, Inc. |
|
* |
|
* 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 <asm/compiler.h> |
|
#include <linux/delay.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/of_platform.h> |
|
#include <linux/platform_device.h> |
|
#ifdef CONFIG_MT_TRUSTY_DEBUGFS |
|
#include <linux/random.h> |
|
#endif |
|
#include <linux/slab.h> |
|
#include <linux/stat.h> |
|
#include <linux/string.h> |
|
#include <linux/trusty/smcall.h> |
|
#include <linux/trusty/sm_err.h> |
|
#include <linux/trusty/trusty.h> |
|
|
|
struct trusty_state { |
|
struct mutex smc_lock; |
|
struct atomic_notifier_head notifier; |
|
struct completion cpu_idle_completion; |
|
char *version_str; |
|
u32 api_version; |
|
}; |
|
|
|
#ifdef CONFIG_ARM64 |
|
#define SMC_ARG0 "x0" |
|
#define SMC_ARG1 "x1" |
|
#define SMC_ARG2 "x2" |
|
#define SMC_ARG3 "x3" |
|
#define SMC_ARCH_EXTENSION "" |
|
#define SMC_REGISTERS_TRASHED "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", \ |
|
"x12", "x13", "x14", "x15", "x16", "x17" |
|
#else |
|
#define SMC_ARG0 "r0" |
|
#define SMC_ARG1 "r1" |
|
#define SMC_ARG2 "r2" |
|
#define SMC_ARG3 "r3" |
|
#define SMC_ARCH_EXTENSION ".arch_extension sec\n" |
|
#define SMC_REGISTERS_TRASHED "ip" |
|
#endif |
|
|
|
static inline ulong smc(ulong r0, ulong r1, ulong r2, ulong r3) |
|
{ |
|
register ulong _r0 asm(SMC_ARG0) = r0; |
|
register ulong _r1 asm(SMC_ARG1) = r1; |
|
register ulong _r2 asm(SMC_ARG2) = r2; |
|
register ulong _r3 asm(SMC_ARG3) = r3; |
|
|
|
asm volatile( |
|
__asmeq("%0", SMC_ARG0) |
|
__asmeq("%1", SMC_ARG1) |
|
__asmeq("%2", SMC_ARG2) |
|
__asmeq("%3", SMC_ARG3) |
|
__asmeq("%4", SMC_ARG0) |
|
__asmeq("%5", SMC_ARG1) |
|
__asmeq("%6", SMC_ARG2) |
|
__asmeq("%7", SMC_ARG3) |
|
SMC_ARCH_EXTENSION |
|
"smc #0" /* switch to secure world */ |
|
: "=r" (_r0), "=r" (_r1), "=r" (_r2), "=r" (_r3) |
|
: "r" (_r0), "r" (_r1), "r" (_r2), "r" (_r3) |
|
: SMC_REGISTERS_TRASHED); |
|
return _r0; |
|
} |
|
|
|
#ifdef CONFIG_TRUSTY_WDT_FIQ_ARMV7_SUPPORT |
|
s32 trusty_fast_call32_nodev(u32 smcnr, u32 a0, u32 a1, u32 a2) |
|
{ |
|
BUG_ON(!SMC_IS_FASTCALL(smcnr)); |
|
BUG_ON(SMC_IS_SMC64(smcnr)); |
|
|
|
return smc(smcnr, a0, a1, a2); |
|
} |
|
#endif |
|
|
|
s32 trusty_fast_call32(struct device *dev, u32 smcnr, u32 a0, u32 a1, u32 a2) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
BUG_ON(!s); |
|
BUG_ON(!SMC_IS_FASTCALL(smcnr)); |
|
BUG_ON(SMC_IS_SMC64(smcnr)); |
|
|
|
return smc(smcnr, a0, a1, a2); |
|
} |
|
EXPORT_SYMBOL(trusty_fast_call32); |
|
|
|
#ifdef CONFIG_64BIT |
|
s64 trusty_fast_call64(struct device *dev, u64 smcnr, u64 a0, u64 a1, u64 a2) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
BUG_ON(!s); |
|
BUG_ON(!SMC_IS_FASTCALL(smcnr)); |
|
BUG_ON(!SMC_IS_SMC64(smcnr)); |
|
|
|
return smc(smcnr, a0, a1, a2); |
|
} |
|
#endif |
|
|
|
static ulong trusty_std_call_inner(struct device *dev, ulong smcnr, |
|
ulong a0, ulong a1, ulong a2) |
|
{ |
|
ulong ret; |
|
int retry = 5; |
|
|
|
dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx)\n", |
|
__func__, smcnr, a0, a1, a2); |
|
while (true) { |
|
ret = smc(smcnr, a0, a1, a2); |
|
while ((s32)ret == SM_ERR_FIQ_INTERRUPTED) |
|
ret = smc(SMC_SC_RESTART_FIQ, 0, 0, 0); |
|
if ((int)ret != SM_ERR_BUSY || !retry) |
|
break; |
|
|
|
dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy, retry\n", |
|
__func__, smcnr, a0, a1, a2); |
|
retry--; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static ulong trusty_std_call_helper(struct device *dev, ulong smcnr, |
|
ulong a0, ulong a1, ulong a2) |
|
{ |
|
ulong ret; |
|
int sleep_time = 1; |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
while (true) { |
|
local_irq_disable(); |
|
atomic_notifier_call_chain(&s->notifier, TRUSTY_CALL_PREPARE, |
|
NULL); |
|
ret = trusty_std_call_inner(dev, smcnr, a0, a1, a2); |
|
atomic_notifier_call_chain(&s->notifier, TRUSTY_CALL_RETURNED, |
|
NULL); |
|
local_irq_enable(); |
|
|
|
if ((int)ret != SM_ERR_BUSY) |
|
break; |
|
|
|
if (sleep_time == 256) |
|
dev_warn(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy\n", |
|
__func__, smcnr, a0, a1, a2); |
|
dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy, wait %d ms\n", |
|
__func__, smcnr, a0, a1, a2, sleep_time); |
|
|
|
msleep(sleep_time); |
|
if (sleep_time < 1000) |
|
sleep_time <<= 1; |
|
|
|
dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) retry\n", |
|
__func__, smcnr, a0, a1, a2); |
|
} |
|
|
|
if (sleep_time > 256) |
|
dev_warn(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) busy cleared\n", |
|
__func__, smcnr, a0, a1, a2); |
|
|
|
return ret; |
|
} |
|
|
|
static void trusty_std_call_cpu_idle(struct trusty_state *s) |
|
{ |
|
int ret; |
|
unsigned long timeout = HZ * 10; |
|
|
|
#ifdef CONFIG_TRUSTY_INTERRUPT_FIQ_ONLY |
|
timeout = HZ / 5; /* 200 ms */ |
|
#endif |
|
|
|
ret = wait_for_completion_timeout(&s->cpu_idle_completion, timeout); |
|
if (!ret) { |
|
pr_warn("%s: timed out waiting for cpu idle to clear, retry anyway\n", |
|
__func__); |
|
} |
|
} |
|
|
|
s32 trusty_std_call32(struct device *dev, u32 smcnr, u32 a0, u32 a1, u32 a2) |
|
{ |
|
int ret; |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
BUG_ON(SMC_IS_FASTCALL(smcnr)); |
|
BUG_ON(SMC_IS_SMC64(smcnr)); |
|
|
|
if (smcnr != SMC_SC_NOP) { |
|
mutex_lock(&s->smc_lock); |
|
reinit_completion(&s->cpu_idle_completion); |
|
} |
|
|
|
dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) started\n", |
|
__func__, smcnr, a0, a1, a2); |
|
|
|
ret = trusty_std_call_helper(dev, smcnr, a0, a1, a2); |
|
while (ret == SM_ERR_INTERRUPTED || ret == SM_ERR_CPU_IDLE) { |
|
dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) interrupted\n", |
|
__func__, smcnr, a0, a1, a2); |
|
if (ret == SM_ERR_CPU_IDLE) |
|
trusty_std_call_cpu_idle(s); |
|
ret = trusty_std_call_helper(dev, SMC_SC_RESTART_LAST, 0, 0, 0); |
|
} |
|
dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) returned 0x%x\n", |
|
__func__, smcnr, a0, a1, a2, ret); |
|
|
|
WARN_ONCE(ret == SM_ERR_PANIC, "trusty crashed"); |
|
|
|
if (smcnr == SMC_SC_NOP) |
|
complete(&s->cpu_idle_completion); |
|
else |
|
mutex_unlock(&s->smc_lock); |
|
|
|
return ret; |
|
} |
|
EXPORT_SYMBOL(trusty_std_call32); |
|
|
|
int trusty_call_notifier_register(struct device *dev, struct notifier_block *n) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
return atomic_notifier_chain_register(&s->notifier, n); |
|
} |
|
EXPORT_SYMBOL(trusty_call_notifier_register); |
|
|
|
int trusty_call_notifier_unregister(struct device *dev, |
|
struct notifier_block *n) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
return atomic_notifier_chain_unregister(&s->notifier, n); |
|
} |
|
EXPORT_SYMBOL(trusty_call_notifier_unregister); |
|
|
|
static int trusty_remove_child(struct device *dev, void *data) |
|
{ |
|
platform_device_unregister(to_platform_device(dev)); |
|
return 0; |
|
} |
|
|
|
ssize_t trusty_version_show(struct device *dev, struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
return scnprintf(buf, PAGE_SIZE, "%s\n", s->version_str); |
|
} |
|
|
|
DEVICE_ATTR(trusty_version, S_IRUSR, trusty_version_show, NULL); |
|
|
|
#ifdef CONFIG_MT_TRUSTY_DEBUGFS |
|
ssize_t trusty_add(struct device *dev, struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
s32 a, b, ret; |
|
|
|
get_random_bytes(&a, sizeof(s32)); |
|
a &= 0xFF; |
|
get_random_bytes(&b, sizeof(s32)); |
|
b &= 0xFF; |
|
ret = trusty_std_call32(dev, MT_SMC_SC_ADD, a, b, 0); |
|
return scnprintf(buf, PAGE_SIZE, "%d + %d = %d, %s\n", a, b, ret, |
|
(a + b) == ret ? "PASS" : "FAIL"); |
|
} |
|
|
|
DEVICE_ATTR(trusty_add, S_IRUSR, trusty_add, NULL); |
|
|
|
ssize_t trusty_threads(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
/* Dump Trusty threads info to memlog */ |
|
trusty_fast_call32(dev, MT_SMC_FC_THREADS, 0, 0, 0); |
|
/* Dump threads info from memlog to kmsg*/ |
|
trusty_std_call32(dev, SMC_SC_NOP, 0, 0, 0); |
|
return 0; |
|
} |
|
|
|
DEVICE_ATTR(trusty_threads, S_IRUSR, trusty_threads, NULL); |
|
|
|
ssize_t trusty_threadstats(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
/* Dump Trusty threads info to memlog */ |
|
trusty_fast_call32(dev, MT_SMC_FC_THREADSTATS, 0, 0, 0); |
|
/* Dump threads info from memlog to kmsg*/ |
|
trusty_std_call32(dev, SMC_SC_NOP, 0, 0, 0); |
|
return 0; |
|
} |
|
|
|
DEVICE_ATTR(trusty_threadstats, S_IRUSR, trusty_threadstats, NULL); |
|
|
|
ssize_t trusty_threadload(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
/* Dump Trusty threads info to memlog */ |
|
trusty_fast_call32(dev, MT_SMC_FC_THREADLOAD, 0, 0, 0); |
|
/* Dump threads info from memlog to kmsg*/ |
|
trusty_std_call32(dev, SMC_SC_NOP, 0, 0, 0); |
|
return 0; |
|
} |
|
|
|
DEVICE_ATTR(trusty_threadload, S_IRUSR, trusty_threadload, NULL); |
|
|
|
ssize_t trusty_heap_dump(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
/* Dump Trusty threads info to memlog */ |
|
trusty_fast_call32(dev, MT_SMC_FC_HEAP_DUMP, 0, 0, 0); |
|
/* Dump threads info from memlog to kmsg*/ |
|
trusty_std_call32(dev, SMC_SC_NOP, 0, 0, 0); |
|
return 0; |
|
} |
|
|
|
DEVICE_ATTR(trusty_heap_dump, S_IRUSR, trusty_heap_dump, NULL); |
|
|
|
ssize_t trusty_apps(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
/* Dump Trusty threads info to memlog */ |
|
trusty_fast_call32(dev, MT_SMC_FC_APPS, 0, 0, 0); |
|
/* Dump threads info from memlog to kmsg*/ |
|
trusty_std_call32(dev, SMC_SC_NOP, 0, 0, 0); |
|
return 0; |
|
} |
|
|
|
DEVICE_ATTR(trusty_apps, S_IRUSR, trusty_apps, NULL); |
|
|
|
ssize_t trusty_vdev_reset(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
trusty_std_call32(dev, SMC_SC_VDEV_RESET, 0, 0, 0); |
|
return 0; |
|
} |
|
|
|
DEVICE_ATTR(trusty_vdev_reset, S_IRUSR, trusty_vdev_reset, NULL); |
|
|
|
|
|
static void trusty_create_debugfs(struct trusty_state *s, struct device *pdev) |
|
{ |
|
int ret; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_add); |
|
if (ret) |
|
goto err_create_trusty_add; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_threads); |
|
if (ret) |
|
goto err_create_trusty_threads; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_threadstats); |
|
if (ret) |
|
goto err_create_trusty_threadstats; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_threadload); |
|
if (ret) |
|
goto err_create_trusty_threadload; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_heap_dump); |
|
if (ret) |
|
goto err_create_trusty_heap_dump; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_apps); |
|
if (ret) |
|
goto err_create_trusty_apps; |
|
|
|
ret = device_create_file(pdev, &dev_attr_trusty_vdev_reset); |
|
if (ret) |
|
goto err_create_trusty_vdev_reset; |
|
|
|
return; |
|
|
|
err_create_trusty_vdev_reset: |
|
device_remove_file(pdev, &dev_attr_trusty_vdev_reset); |
|
err_create_trusty_apps: |
|
device_remove_file(pdev, &dev_attr_trusty_apps); |
|
err_create_trusty_heap_dump: |
|
device_remove_file(pdev, &dev_attr_trusty_heap_dump); |
|
err_create_trusty_threadload: |
|
device_remove_file(pdev, &dev_attr_trusty_threadload); |
|
err_create_trusty_threadstats: |
|
device_remove_file(pdev, &dev_attr_trusty_threadstats); |
|
err_create_trusty_threads: |
|
device_remove_file(pdev, &dev_attr_trusty_threads); |
|
err_create_trusty_add: |
|
device_remove_file(pdev, &dev_attr_trusty_add); |
|
} |
|
|
|
#endif /* CONFIG_MT_TRUSTY_DEBUGFS */ |
|
|
|
const char *trusty_version_str_get(struct device *dev) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
return s->version_str; |
|
} |
|
EXPORT_SYMBOL(trusty_version_str_get); |
|
|
|
static void trusty_init_version(struct trusty_state *s, struct device *dev) |
|
{ |
|
int ret; |
|
int i; |
|
int version_str_len; |
|
|
|
ret = trusty_fast_call32(dev, SMC_FC_GET_VERSION_STR, -1, 0, 0); |
|
if (ret <= 0) |
|
goto err_get_size; |
|
|
|
version_str_len = ret; |
|
|
|
s->version_str = kmalloc(version_str_len + 1, GFP_KERNEL); |
|
for (i = 0; i < version_str_len; i++) { |
|
ret = trusty_fast_call32(dev, SMC_FC_GET_VERSION_STR, i, 0, 0); |
|
if (ret < 0) |
|
goto err_get_char; |
|
s->version_str[i] = ret; |
|
} |
|
s->version_str[i] = '\0'; |
|
|
|
dev_info(dev, "trusty version: %s\n", s->version_str); |
|
|
|
ret = device_create_file(dev, &dev_attr_trusty_version); |
|
if (ret) |
|
goto err_create_file; |
|
|
|
return; |
|
|
|
err_create_file: |
|
err_get_char: |
|
kfree(s->version_str); |
|
s->version_str = NULL; |
|
err_get_size: |
|
dev_err(dev, "failed to get version: %d\n", ret); |
|
} |
|
|
|
u32 trusty_get_api_version(struct device *dev) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
|
|
|
return s->api_version; |
|
} |
|
EXPORT_SYMBOL(trusty_get_api_version); |
|
|
|
static int trusty_init_api_version(struct trusty_state *s, struct device *dev) |
|
{ |
|
u32 api_version; |
|
|
|
api_version = trusty_fast_call32(dev, SMC_FC_API_VERSION, |
|
TRUSTY_API_VERSION_CURRENT, 0, 0); |
|
if (api_version == SM_ERR_UNDEFINED_SMC) |
|
api_version = 0; |
|
|
|
if (api_version > TRUSTY_API_VERSION_CURRENT) { |
|
dev_err(dev, "unsupported api version %u > %u\n", |
|
api_version, TRUSTY_API_VERSION_CURRENT); |
|
return -EINVAL; |
|
} |
|
|
|
dev_info(dev, "selected api version: %u (requested %u)\n", |
|
api_version, TRUSTY_API_VERSION_CURRENT); |
|
s->api_version = api_version; |
|
|
|
return 0; |
|
} |
|
|
|
static int trusty_probe(struct platform_device *pdev) |
|
{ |
|
int ret; |
|
struct trusty_state *s; |
|
struct device_node *node = pdev->dev.of_node; |
|
|
|
if (!node) { |
|
dev_err(&pdev->dev, "of_node required\n"); |
|
return -EINVAL; |
|
} |
|
|
|
s = kzalloc(sizeof(*s), GFP_KERNEL); |
|
if (!s) { |
|
ret = -ENOMEM; |
|
goto err_allocate_state; |
|
} |
|
mutex_init(&s->smc_lock); |
|
ATOMIC_INIT_NOTIFIER_HEAD(&s->notifier); |
|
init_completion(&s->cpu_idle_completion); |
|
platform_set_drvdata(pdev, s); |
|
|
|
trusty_init_version(s, &pdev->dev); |
|
|
|
ret = trusty_init_api_version(s, &pdev->dev); |
|
if (ret < 0) |
|
goto err_api_version; |
|
|
|
ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); |
|
if (ret < 0) { |
|
dev_err(&pdev->dev, "Failed to add children: %d\n", ret); |
|
goto err_add_children; |
|
} |
|
|
|
#ifdef CONFIG_MT_TRUSTY_DEBUGFS |
|
trusty_create_debugfs(s, &pdev->dev); |
|
#endif |
|
|
|
return 0; |
|
|
|
err_add_children: |
|
err_api_version: |
|
if (s->version_str) { |
|
device_remove_file(&pdev->dev, &dev_attr_trusty_version); |
|
kfree(s->version_str); |
|
} |
|
device_for_each_child(&pdev->dev, NULL, trusty_remove_child); |
|
mutex_destroy(&s->smc_lock); |
|
kfree(s); |
|
err_allocate_state: |
|
return ret; |
|
} |
|
|
|
static int trusty_remove(struct platform_device *pdev) |
|
{ |
|
struct trusty_state *s = platform_get_drvdata(pdev); |
|
|
|
device_for_each_child(&pdev->dev, NULL, trusty_remove_child); |
|
mutex_destroy(&s->smc_lock); |
|
if (s->version_str) { |
|
device_remove_file(&pdev->dev, &dev_attr_trusty_version); |
|
kfree(s->version_str); |
|
} |
|
kfree(s); |
|
return 0; |
|
} |
|
|
|
static const struct of_device_id trusty_of_match[] = { |
|
{ .compatible = "android,trusty-smc-v1", }, |
|
{}, |
|
}; |
|
|
|
static struct platform_driver trusty_driver = { |
|
.probe = trusty_probe, |
|
.remove = trusty_remove, |
|
.driver = { |
|
.name = "trusty", |
|
.owner = THIS_MODULE, |
|
.of_match_table = trusty_of_match, |
|
}, |
|
}; |
|
|
|
static int __init trusty_driver_init(void) |
|
{ |
|
return platform_driver_register(&trusty_driver); |
|
} |
|
|
|
static void __exit trusty_driver_exit(void) |
|
{ |
|
platform_driver_unregister(&trusty_driver); |
|
} |
|
|
|
subsys_initcall(trusty_driver_init); |
|
module_exit(trusty_driver_exit);
|
|
|