This commit expands RPC support for the OP-TEE driver using 2 files:
1) drivers/misc/optee_rpc.c
* Add support for RPCs that can be handled directly by the kernel.
* Can delegate RPC handling to optee_supplicant.c for RPCs that
need userspace interaction.
2) drivers/misc/optee_supplicant.c
* Enable communication between the userspace TEE supplicant and the
kernel driver.
Additional changes were needed to the following files:
1) drivers/misc/optee.c
* Add ioctls used SOLELY by the userspace TEE supplicant.
* Register /dev/teepriv0 if the supplicant is enabled in Kconfig
* Add OPTEE_ROLE_CA and OPTEE_ROLE_SUPPLICANT conditionals to
differentiate paths, between a normal Client Application (CA)
and the TEE supplicant.
* Change some functions from static to "public" to reuse them
in other C files.
* Adjust optee_to/from_msg_param() to work with RPCs.
2) drivers/misc/optee_smc.c
* Call the RPC handler from optee_rpc.c
3) drivers/misc/optee_msg.h
* Add definition needed for RPCs
4) drivers/misc/tee.h
* Add ioctl definitions
* Add TEE_SHM_SUPP flag, checked when unregistering supplicant
memory.
5) Documentation/guides/optee.rs
* Add documentation for RPCs and the supplicant.
6) drivers/misc/{CMakeLists.txt, Make.defs}
* Account for the new files.
7) drivers/misc/Kconfig
* Add DEV_OPTEE_SUPPLICANT option to enable/disable the supplicant
driver.
Signed-off-by: Theodore Karatapanis <tkaratapanis@census-labs.com>
364 lines
10 KiB
C
364 lines
10 KiB
C
/****************************************************************************
|
|
* drivers/misc/optee_smc.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/cache.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <errno.h>
|
|
#include <syslog.h>
|
|
#include <string.h>
|
|
#include <sched.h>
|
|
|
|
#include "optee.h"
|
|
#include "optee_smc.h"
|
|
#include "optee_rpc.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_ARCH_ARM)
|
|
# define smccc_smc arm_smccc_smc
|
|
# define smccc_hvc arm_smccc_hvc
|
|
# define smccc_res_t arm_smccc_res_t
|
|
#elif defined(CONFIG_ARCH_ARM64)
|
|
# define smccc_smc arm64_smccc_smc
|
|
# define smccc_hvc arm64_smccc_hvc
|
|
# define smccc_res_t arm64_smccc_res_t
|
|
#else
|
|
# error "CONFIG_DEV_OPTEE_SMC is only supported on arm and arm64"
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEV_OPTEE_SMC_CONDUIT_SMC
|
|
# define smc_conduit smccc_smc
|
|
#else
|
|
# define smc_conduit smccc_hvc
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
typedef void (*optee_smc_fn)(unsigned long, unsigned long, unsigned long,
|
|
unsigned long, unsigned long, unsigned long,
|
|
unsigned long, unsigned long,
|
|
FAR smccc_res_t *);
|
|
|
|
struct optee_smc_priv_data
|
|
{
|
|
struct optee_priv_data base;
|
|
optee_smc_fn smc_fn;
|
|
};
|
|
|
|
union optee_smc_os_revision
|
|
{
|
|
smccc_res_t smccc;
|
|
struct optee_smc_call_get_os_revision_result result;
|
|
};
|
|
|
|
union optee_smc_calls_revision
|
|
{
|
|
smccc_res_t smccc;
|
|
struct optee_smc_calls_revision_result result;
|
|
};
|
|
|
|
union optee_smc_exchg_caps
|
|
{
|
|
smccc_res_t smccc;
|
|
struct optee_smc_exchange_capabilities_result result;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static inline FAR void *reg_pair_to_ptr(uint64_t reg0, uint64_t reg1)
|
|
{
|
|
return (FAR void *)(uintptr_t)(reg0 << 32 | (reg1 & UINT32_MAX));
|
|
}
|
|
|
|
static inline void reg_pair_from_64(uint64_t val, uint64_t *reg0,
|
|
uint64_t *reg1)
|
|
{
|
|
*reg0 = val >> 32;
|
|
*reg1 = val & UINT32_MAX;
|
|
}
|
|
|
|
static bool optee_smc_is_compatible(optee_smc_fn smc_fn)
|
|
{
|
|
union optee_smc_os_revision osrev;
|
|
union optee_smc_calls_revision callsrev;
|
|
union optee_smc_exchg_caps xchgcaps;
|
|
smccc_res_t callsuid;
|
|
|
|
/* Print the OS revision and build ID (if reported) */
|
|
|
|
osrev.result.build_id = 0;
|
|
|
|
smc_fn(OPTEE_SMC_CALL_GET_OS_REVISION, 0, 0, 0, 0, 0, 0, 0, &osrev.smccc);
|
|
|
|
if (osrev.result.build_id)
|
|
{
|
|
syslog(LOG_INFO, "OP-TEE: OS revision %lu.%lu (%08lx)\n",
|
|
osrev.result.major, osrev.result.minor,
|
|
osrev.result.build_id);
|
|
}
|
|
else
|
|
{
|
|
syslog(LOG_INFO, "OP-TEE: OS revision %lu.%lu\n",
|
|
osrev.result.major, osrev.result.minor);
|
|
}
|
|
|
|
/* Check the API UID */
|
|
|
|
smc_fn(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &callsuid);
|
|
|
|
if (callsuid.a0 != OPTEE_MSG_UID_0 || callsuid.a1 != OPTEE_MSG_UID_1 ||
|
|
callsuid.a2 != OPTEE_MSG_UID_2 || callsuid.a3 != OPTEE_MSG_UID_3)
|
|
{
|
|
syslog(LOG_ERR, "OP-TEE: API UID mismatch\n");
|
|
return false;
|
|
}
|
|
|
|
/* Check the API revision */
|
|
|
|
smc_fn(OPTEE_SMC_CALLS_REVISION, 0, 0, 0, 0, 0, 0, 0, &callsrev.smccc);
|
|
|
|
if (callsrev.result.major != OPTEE_MSG_REVISION_MAJOR ||
|
|
callsrev.result.minor < OPTEE_MSG_REVISION_MINOR)
|
|
{
|
|
syslog(LOG_ERR, "OP-TEE: API revision incompatible\n");
|
|
return false;
|
|
}
|
|
|
|
/* Check the capabilities */
|
|
|
|
smc_fn(OPTEE_SMC_EXCHANGE_CAPABILITIES, OPTEE_SMC_NSEC_CAP_UNIPROCESSOR,
|
|
0, 0, 0, 0, 0, 0, &xchgcaps.smccc);
|
|
|
|
if (xchgcaps.result.status != OPTEE_SMC_RETURN_OK)
|
|
{
|
|
syslog(LOG_ERR, "OP-TEE: Failed to exchange capabilities\n");
|
|
return false;
|
|
}
|
|
|
|
if (!(xchgcaps.result.capabilities & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM))
|
|
{
|
|
syslog(LOG_ERR, "OP-TEE: Does not support dynamic shared mem\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void optee_smc_handle_rpc(FAR struct optee_priv_data *priv_,
|
|
FAR smccc_res_t *par,
|
|
FAR void **last_page_list)
|
|
{
|
|
FAR struct optee_shm *shm;
|
|
uint32_t rpc_func;
|
|
|
|
rpc_func = OPTEE_SMC_RETURN_GET_RPC_FUNC(par->a0);
|
|
par->a0 = OPTEE_SMC_CALL_RETURN_FROM_RPC;
|
|
|
|
switch (rpc_func)
|
|
{
|
|
case OPTEE_SMC_RPC_FUNC_ALLOC:
|
|
if (!optee_shm_alloc(priv_, NULL, par->a1, TEE_SHM_ALLOC, &shm))
|
|
{
|
|
reg_pair_from_64(shm->paddr, &par->a1, &par->a2);
|
|
reg_pair_from_64((uintptr_t)shm, &par->a4, &par->a5);
|
|
}
|
|
else
|
|
{
|
|
reg_pair_from_64(0, &par->a1, &par->a2);
|
|
reg_pair_from_64(0, &par->a4, &par->a5);
|
|
}
|
|
break;
|
|
|
|
case OPTEE_SMC_RPC_FUNC_FREE:
|
|
shm = reg_pair_to_ptr(par->a1, par->a2);
|
|
optee_shm_free(shm);
|
|
break;
|
|
|
|
case OPTEE_SMC_RPC_FUNC_FOREIGN_INTR:
|
|
|
|
/* yield to tasks of same priority to avoid starving the NW */
|
|
|
|
sched_yield();
|
|
break;
|
|
case OPTEE_SMC_RPC_FUNC_CMD:
|
|
shm = reg_pair_to_ptr(par->a1, par->a2);
|
|
optee_rpc_handle_cmd(priv_, shm, last_page_list);
|
|
break;
|
|
|
|
default:
|
|
syslog(LOG_ERR, "OP-TEE: RPC 0x%04x not implemented\n", rpc_func);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: optee_transport_init
|
|
*
|
|
* Description:
|
|
* Perform any initialization actions specific to the transport used
|
|
* right before the driver is registered.
|
|
*
|
|
* Returned Values:
|
|
* 0 on success; A negated errno value is returned on any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int optee_transport_init(void)
|
|
{
|
|
if (!optee_smc_is_compatible(smc_conduit))
|
|
{
|
|
return -ENOENT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_transport_open
|
|
*
|
|
* Description:
|
|
* Perform any transport-specific actions upon driver character device
|
|
* open.
|
|
*
|
|
* Parameters:
|
|
* priv_ - the optee_priv_data struct to allocate and return by
|
|
* reference.
|
|
*
|
|
* Returned Values:
|
|
* 0 on success; A negated errno value is returned on any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int optee_transport_open(FAR struct optee_priv_data **priv_)
|
|
{
|
|
FAR struct optee_smc_priv_data *priv;
|
|
|
|
priv = kmm_zalloc(sizeof(struct optee_smc_priv_data));
|
|
if (priv == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->base.alignment = OPTEE_MSG_NONCONTIG_PAGE_SIZE;
|
|
priv->smc_fn = smc_conduit;
|
|
*priv_ = (FAR struct optee_priv_data *)priv;
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_transport_close
|
|
*
|
|
* Description:
|
|
* Perform any transport-specific actions upon driver character device
|
|
* close.
|
|
*
|
|
* Parameters:
|
|
* priv_ - the optee_priv_data struct to close and de-allocate.
|
|
*
|
|
* Returned Values:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void optee_transport_close(FAR struct optee_priv_data *priv_)
|
|
{
|
|
kmm_free(priv_);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_transport_call
|
|
*
|
|
* Description:
|
|
* Call OP-TEE OS using SMCs.
|
|
*
|
|
* Parameters:
|
|
* priv_ - the optee_priv_data struct to use
|
|
*
|
|
* Returned Values:
|
|
* 0 on success; A negated errno value is returned on any failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int optee_transport_call(FAR struct optee_priv_data *priv_,
|
|
FAR struct optee_msg_arg *arg)
|
|
{
|
|
FAR struct optee_smc_priv_data *priv =
|
|
(FAR struct optee_smc_priv_data *)priv_;
|
|
smccc_res_t res;
|
|
smccc_res_t par;
|
|
uintptr_t arg_pa;
|
|
#ifndef CONFIG_ARCH_USE_MMU
|
|
size_t arg_size = OPTEE_MSG_GET_ARG_SIZE(arg->num_params);
|
|
|
|
up_clean_dcache((uintptr_t)arg, (uintptr_t)arg + arg_size);
|
|
#endif
|
|
FAR void *last_page_list = NULL;
|
|
|
|
memset(&par, 0, sizeof(smccc_res_t));
|
|
|
|
par.a0 = OPTEE_SMC_CALL_WITH_ARG;
|
|
arg_pa = optee_va_to_pa(arg);
|
|
|
|
reg_pair_from_64(arg_pa, &par.a1, &par.a2);
|
|
|
|
for (; ; )
|
|
{
|
|
memset(&res, 0, sizeof(smccc_res_t));
|
|
|
|
priv->smc_fn(par.a0, par.a1, par.a2, par.a3,
|
|
par.a4, par.a5, par.a6, par.a7, &res);
|
|
|
|
if (OPTEE_SMC_RETURN_IS_RPC(res.a0))
|
|
{
|
|
memcpy(&par, &res, 4 * sizeof(unsigned long));
|
|
optee_smc_handle_rpc(priv_, &par, &last_page_list);
|
|
}
|
|
else
|
|
{
|
|
#ifndef CONFIG_ARCH_USE_MMU
|
|
up_invalidate_dcache((uintptr_t)arg, (uintptr_t)arg + arg_size);
|
|
#endif
|
|
|
|
kmm_free(last_page_list);
|
|
last_page_list = NULL;
|
|
return (int)res.a0;
|
|
}
|
|
}
|
|
}
|