blob: 36e85897b312fe507251e5a5808c293ad8edc867 [file] [log] [blame]
/*
* 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/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#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;
char *version_str;
};
#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;
}
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);
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;
}
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));
mutex_lock(&s->smc_lock);
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) {
dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) interrupted\n",
__func__, smcnr, a0, a1, a2);
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);
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);
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);
}
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);
platform_set_drvdata(pdev, s);
trusty_init_version(s, &pdev->dev);
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;
}
return 0;
err_add_children:
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);