258 lines
7.8 KiB
C
258 lines
7.8 KiB
C
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy,
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <trusty/trusty_dev.h>
|
|
#include <trusty/util.h>
|
|
|
|
#include "sm_err.h"
|
|
#include "smcall.h"
|
|
|
|
struct trusty_dev;
|
|
|
|
#define LOCAL_LOG 0
|
|
|
|
#ifndef __asmeq
|
|
#define __asmeq(x, y) ".ifnc " x "," y " ; .err ; .endif\n\t"
|
|
#endif
|
|
|
|
#ifdef NS_ARCH_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
|
|
|
|
/*
|
|
* Execute SMC call into trusty
|
|
*/
|
|
static unsigned long smc(unsigned long r0,
|
|
unsigned long r1,
|
|
unsigned long r2,
|
|
unsigned long r3)
|
|
{
|
|
register unsigned long _r0 __asm__(SMC_ARG0) = r0;
|
|
register unsigned long _r1 __asm__(SMC_ARG1) = r1;
|
|
register unsigned long _r2 __asm__(SMC_ARG2) = r2;
|
|
register unsigned long _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;
|
|
}
|
|
|
|
static int32_t trusty_fast_call32(struct trusty_dev *dev, uint32_t smcnr,
|
|
uint32_t a0, uint32_t a1, uint32_t a2)
|
|
{
|
|
trusty_assert(dev);
|
|
trusty_assert(SMC_IS_FASTCALL(smcnr));
|
|
|
|
return smc(smcnr, a0, a1, a2);
|
|
}
|
|
|
|
static unsigned long trusty_std_call_inner(struct trusty_dev *dev,
|
|
unsigned long smcnr,
|
|
unsigned long a0,
|
|
unsigned long a1,
|
|
unsigned long a2)
|
|
{
|
|
unsigned long ret;
|
|
int retry = 5;
|
|
|
|
trusty_debug("%s(0x%lx 0x%lx 0x%lx 0x%lx)\n", __func__, smcnr, a0, a1, a2);
|
|
|
|
while (true) {
|
|
ret = smc(smcnr, a0, a1, a2);
|
|
while ((int32_t)ret == SM_ERR_FIQ_INTERRUPTED)
|
|
ret = smc(SMC_SC_RESTART_FIQ, 0, 0, 0);
|
|
if ((int)ret != SM_ERR_BUSY || !retry)
|
|
break;
|
|
|
|
trusty_debug("%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy, retry\n",
|
|
__func__, smcnr, a0, a1, a2);
|
|
|
|
retry--;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long trusty_std_call_helper(struct trusty_dev *dev,
|
|
unsigned long smcnr,
|
|
unsigned long a0,
|
|
unsigned long a1,
|
|
unsigned long a2)
|
|
{
|
|
unsigned long ret;
|
|
unsigned long irq_state;
|
|
|
|
while (true) {
|
|
trusty_local_irq_disable(&irq_state);
|
|
ret = trusty_std_call_inner(dev, smcnr, a0, a1, a2);
|
|
trusty_local_irq_restore(&irq_state);
|
|
|
|
if ((int)ret != SM_ERR_BUSY)
|
|
break;
|
|
|
|
trusty_idle(dev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int32_t trusty_std_call32(struct trusty_dev *dev, uint32_t smcnr,
|
|
uint32_t a0, uint32_t a1, uint32_t a2)
|
|
{
|
|
int ret;
|
|
|
|
trusty_assert(dev);
|
|
trusty_assert(!SMC_IS_FASTCALL(smcnr));
|
|
|
|
if (smcnr != SMC_SC_NOP) {
|
|
trusty_lock(dev);
|
|
}
|
|
|
|
trusty_debug("%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) {
|
|
trusty_debug("%s(0x%x 0x%x 0x%x 0x%x) interrupted\n", __func__,
|
|
smcnr, a0, a1, a2);
|
|
if (ret == SM_ERR_CPU_IDLE) {
|
|
trusty_idle(dev);
|
|
}
|
|
ret = trusty_std_call_helper(dev, SMC_SC_RESTART_LAST, 0, 0, 0);
|
|
}
|
|
|
|
trusty_debug("%s(0x%x 0x%x 0x%x 0x%x) returned 0x%x\n",
|
|
__func__, smcnr, a0, a1, a2, ret);
|
|
|
|
if (smcnr != SMC_SC_NOP) {
|
|
trusty_unlock(dev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int trusty_call32_mem_buf(struct trusty_dev *dev, uint32_t smcnr,
|
|
struct ns_mem_page_info *page, uint32_t size)
|
|
{
|
|
trusty_assert(dev);
|
|
trusty_assert(page);
|
|
|
|
if (SMC_IS_FASTCALL(smcnr)) {
|
|
return trusty_fast_call32(dev, smcnr,
|
|
(uint32_t)page->attr,
|
|
(uint32_t)(page->attr >> 32), size);
|
|
} else {
|
|
return trusty_std_call32(dev, smcnr,
|
|
(uint32_t)page->attr,
|
|
(uint32_t)(page->attr >> 32), size);
|
|
}
|
|
}
|
|
|
|
int trusty_dev_init_ipc(struct trusty_dev *dev,
|
|
struct ns_mem_page_info *buf, uint32_t buf_size)
|
|
{
|
|
return trusty_call32_mem_buf(dev, SMC_SC_TRUSTY_IPC_CREATE_QL_DEV,
|
|
buf, buf_size);
|
|
}
|
|
|
|
int trusty_dev_exec_ipc(struct trusty_dev *dev,
|
|
struct ns_mem_page_info *buf, uint32_t buf_size)
|
|
{
|
|
return trusty_call32_mem_buf(dev, SMC_SC_TRUSTY_IPC_HANDLE_QL_DEV_CMD,
|
|
buf, buf_size);
|
|
}
|
|
|
|
int trusty_dev_shutdown_ipc(struct trusty_dev *dev,
|
|
struct ns_mem_page_info *buf, uint32_t buf_size)
|
|
{
|
|
return trusty_call32_mem_buf(dev, SMC_SC_TRUSTY_IPC_SHUTDOWN_QL_DEV,
|
|
buf, buf_size);
|
|
}
|
|
|
|
|
|
static int trusty_init_api_version(struct trusty_dev *dev)
|
|
{
|
|
uint32_t 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) {
|
|
trusty_error("unsupported trusty api version %u > %u\n",
|
|
api_version, TRUSTY_API_VERSION_CURRENT);
|
|
return -1;
|
|
}
|
|
|
|
trusty_info("selected trusty api version: %u (requested %u)\n",
|
|
api_version, TRUSTY_API_VERSION_CURRENT);
|
|
|
|
dev->api_version = api_version;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int trusty_dev_init(struct trusty_dev *dev, void *priv_data)
|
|
{
|
|
trusty_assert(dev);
|
|
|
|
dev->priv_data = priv_data;
|
|
return trusty_init_api_version(dev);
|
|
}
|
|
|
|
int trusty_dev_shutdown(struct trusty_dev *dev)
|
|
{
|
|
trusty_assert(dev);
|
|
|
|
dev->priv_data = NULL;
|
|
return 0;
|
|
}
|
|
|