blob: 92901e2558f1ef75a71ccf523386340d0f33b73b [file] [log] [blame]
/*
* 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/arm_ffa.h>
#include <trusty/sm_err.h>
#include <trusty/smc.h>
#include <trusty/smcall.h>
#include <trusty/trusty_dev.h>
#include <trusty/trusty_mem.h>
#include <trusty/util.h>
struct trusty_dev;
#define LOCAL_LOG 0
/*
* Select RXTX map smc variant based on register size. Note that the FF-A spec
* does not support passing a 64 bit paddr from a 32 bit client, so the
* allocated buffer has to be below 4G if this is called from 32 bit code.
*/
#define SMC_FCZ_FFA_RXTX_MAP \
((sizeof(unsigned long) <= 4) ? SMC_FC_FFA_RXTX_MAP : SMC_FC64_FFA_RXTX_MAP)
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, false);
}
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, false);
}
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_id(struct trusty_dev* dev,
uint32_t smcnr,
trusty_shared_mem_id_t buf_id,
uint32_t size) {
trusty_assert(dev);
if (SMC_IS_FASTCALL(smcnr)) {
return trusty_fast_call32(dev, smcnr, (uint32_t)buf_id,
(uint32_t)(buf_id >> 32), size);
} else {
return trusty_std_call32(dev, smcnr, (uint32_t)buf_id,
(uint32_t)(buf_id >> 32), size);
}
}
int trusty_dev_init_ipc(struct trusty_dev* dev,
trusty_shared_mem_id_t buf_id,
uint32_t buf_size) {
return trusty_call32_mem_buf_id(dev, SMC_SC_TRUSTY_IPC_CREATE_QL_DEV,
buf_id, buf_size);
}
int trusty_dev_exec_ipc(struct trusty_dev* dev,
trusty_shared_mem_id_t buf_id,
uint32_t buf_size) {
return trusty_call32_mem_buf_id(dev, SMC_SC_TRUSTY_IPC_HANDLE_QL_DEV_CMD,
buf_id, buf_size);
}
int trusty_dev_exec_fc_ipc(struct trusty_dev* dev,
trusty_shared_mem_id_t buf_id,
uint32_t buf_size) {
return trusty_call32_mem_buf_id(dev, SMC_FC_HANDLE_QL_TIPC_DEV_CMD, buf_id,
buf_size);
}
int trusty_dev_shutdown_ipc(struct trusty_dev* dev,
trusty_shared_mem_id_t buf_id,
uint32_t buf_size) {
return trusty_call32_mem_buf_id(dev, SMC_SC_TRUSTY_IPC_SHUTDOWN_QL_DEV,
buf_id, 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) {
int ret;
struct smc_ret8 smc_ret;
struct ns_mem_page_info tx_pinfo;
struct ns_mem_page_info rx_pinfo;
const size_t rxtx_page_count = 1;
trusty_assert(dev);
dev->priv_data = priv_data;
dev->ffa_tx = NULL;
ret = trusty_init_api_version(dev);
if (ret) {
return ret;
}
if (dev->api_version < TRUSTY_API_VERSION_MEM_OBJ) {
return 0;
}
/* Get supported FF-A version and check if it is compatible */
smc_ret = smc8(SMC_FC_FFA_VERSION, FFA_CURRENT_VERSION, 0, 0, 0, 0, 0, 0);
if (FFA_VERSION_TO_MAJOR(smc_ret.r0) != FFA_CURRENT_VERSION_MAJOR) {
/* TODO: support more than one (minor) version. */
trusty_error("%s: unsupported FF-A version 0x%lx, expected 0x%x\n",
__func__, smc_ret.r0, FFA_CURRENT_VERSION);
goto err_version;
}
/* Check that SMC_FC_FFA_MEM_SHARE is implemented */
smc_ret = smc8(SMC_FC_FFA_FEATURES, SMC_FC_FFA_MEM_SHARE, 0, 0, 0, 0, 0, 0);
if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) {
trusty_error(
"%s: SMC_FC_FFA_FEATURES(SMC_FC_FFA_MEM_SHARE) failed 0x%lx 0x%lx 0x%lx\n",
__func__, smc_ret.r0, smc_ret.r1, smc_ret.r2);
goto err_features;
}
/*
* Set FF-A endpoint IDs.
*
* Hardcode 0x8000 for the secure os.
* TODO: Use FFA call or device tree to configure this dynamically
*/
smc_ret = smc8(SMC_FC_FFA_ID_GET, 0, 0, 0, 0, 0, 0, 0);
if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) {
trusty_error("%s: SMC_FC_FFA_ID_GET failed 0x%lx 0x%lx 0x%lx\n",
__func__, smc_ret.r0, smc_ret.r1, smc_ret.r2);
goto err_id_get;
}
dev->ffa_local_id = smc_ret.r2;
dev->ffa_remote_id = 0x8000;
dev->ffa_tx = trusty_alloc_pages(rxtx_page_count);
if (!dev->ffa_tx) {
goto err_alloc_ffa_tx;
}
dev->ffa_rx = trusty_alloc_pages(rxtx_page_count);
if (!dev->ffa_rx) {
goto err_alloc_ffa_rx;
}
ret = trusty_encode_page_info(&tx_pinfo, dev->ffa_tx);
if (ret) {
goto err_encode_page_info;
}
ret = trusty_encode_page_info(&rx_pinfo, dev->ffa_rx);
if (ret) {
goto err_encode_page_info;
}
/*
* TODO: check or pass memory attributes. The FF-A spec says the buffer has
* to be cached, but we currently have callers that don't match this.
*/
smc_ret = smc8(SMC_FCZ_FFA_RXTX_MAP, tx_pinfo.paddr, rx_pinfo.paddr,
rxtx_page_count, 0, 0, 0, 0);
if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) {
trusty_error("%s: FFA_RXTX_MAP failed 0x%lx 0x%lx 0x%lx\n", __func__,
smc_ret.r0, smc_ret.r1, smc_ret.r2);
goto err_rxtx_map;
}
if (ret) {
goto err_setup_msg_buf;
}
return 0;
err_setup_msg_buf:
err_rxtx_map:
err_encode_page_info:
err_alloc_ffa_rx:
err_alloc_ffa_tx:
err_id_get:
err_features:
err_version:
trusty_fatal("%s: init failed\n", __func__, ret);
}
int trusty_dev_shutdown(struct trusty_dev* dev) {
trusty_assert(dev);
if (dev->ffa_tx) {
smc(SMC_FC_FFA_RXTX_UNMAP, 0, 0, 0);
}
dev->priv_data = NULL;
return 0;
}
int trusty_dev_nop(struct trusty_dev* dev) {
int ret = trusty_std_call32(dev, SMC_SC_NOP, 0, 0, 0);
return ret == SM_ERR_NOP_DONE ? 0 : ret == SM_ERR_NOP_INTERRUPTED ? 1 : -1;
}
int trusty_dev_share_memory(struct trusty_dev* dev,
trusty_shared_mem_id_t* idp,
struct ns_mem_page_info* pinfo,
size_t page_count) {
struct smc_ret8 smc_ret;
struct ffa_mtd* mtd = dev->ffa_tx;
size_t comp_mrd_offset = offsetof(struct ffa_mtd, emad[1]);
struct ffa_comp_mrd* comp_mrd = dev->ffa_tx + comp_mrd_offset;
struct ffa_cons_mrd* cons_mrd = comp_mrd->address_range_array;
size_t tx_size = ((void*)cons_mrd - dev->ffa_tx) + sizeof(*cons_mrd);
if (!dev->ffa_tx) {
/*
* If the trusty api version is before TRUSTY_API_VERSION_MEM_OBJ, fall
* back to old api of passing the 64 bit paddr/attr value directly.
*/
*idp = pinfo->attr;
return 0;
}
trusty_memset(mtd, 0, tx_size);
mtd->sender_id = dev->ffa_local_id;
mtd->memory_region_attributes = pinfo->ffa_mem_attr;
mtd->emad_count = 1;
mtd->emad[0].mapd.endpoint_id = dev->ffa_remote_id;
mtd->emad[0].mapd.memory_access_permissions = pinfo->ffa_mem_perm;
mtd->emad[0].comp_mrd_offset = comp_mrd_offset;
comp_mrd->total_page_count = page_count;
comp_mrd->address_range_count = 1;
cons_mrd->address = pinfo->paddr;
cons_mrd->page_count = page_count;
/*
* Tell the SPM/Hypervisor to share the memory.
*/
smc_ret = smc8(SMC_FC_FFA_MEM_SHARE, tx_size, tx_size, 0, 0, 0, 0, 0);
if ((unsigned int)smc_ret.r0 != SMC_FC_FFA_SUCCESS) {
trusty_error("%s: SMC_FC_FFA_MEM_SHARE failed 0x%lx 0x%lx 0x%lx\n",
__func__, smc_ret.r0, smc_ret.r1, smc_ret.r2);
return -1;
}
*idp = smc_ret.r2;
return 0;
}
int trusty_dev_reclaim_memory(struct trusty_dev* dev,
trusty_shared_mem_id_t id) {
struct smc_ret8 smc_ret;
if (!dev->ffa_tx) {
/*
* If the trusty api version is before TRUSTY_API_VERSION_MEM_OBJ, fall
* back to old api.
*/
return 0;
}
/*
* Tell the SPM/Hypervisor to reclaim the memory. If the memory is still in
* use this will fail.
*/
smc_ret =
smc8(SMC_FC_FFA_MEM_RECLAIM, (uint32_t)id, id >> 32, 0, 0, 0, 0, 0);
if ((unsigned int)smc_ret.r0 != SMC_FC_FFA_SUCCESS) {
trusty_error("%s: SMC_FC_FFA_MEM_RECLAIM failed 0x%lx 0x%lx 0x%lx\n",
__func__, smc_ret.r0, smc_ret.r1, smc_ret.r2);
return -1;
}
return 0;
}