This commit simplifies sending errors back to OP-TEE and avoids code duplication when handling errno values. Signed-off-by: Theodore Karatapanis <tkaratapanis@census-labs.com>
599 lines
16 KiB
C
599 lines
16 KiB
C
/****************************************************************************
|
|
* drivers/misc/optee_supplicant.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/mutex.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/queue.h>
|
|
#include <nuttx/idr.h>
|
|
#include <string.h>
|
|
|
|
#include "optee.h"
|
|
#include "optee_supplicant.h"
|
|
#include "optee_msg.h"
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* Request structure for RPCs serviced by the supplicant. */
|
|
|
|
struct optee_supplicant_req
|
|
{
|
|
sq_entry_t link;
|
|
uint32_t func;
|
|
uint32_t ret;
|
|
uint32_t num_params;
|
|
FAR struct tee_ioctl_param *params;
|
|
sem_t c;
|
|
};
|
|
|
|
struct optee_supplicant
|
|
{
|
|
mutex_t mutex;
|
|
int req_id;
|
|
struct sq_queue_s reqs;
|
|
FAR struct idr_s *idr;
|
|
FAR struct idr_s *shm_idr;
|
|
sem_t reqs_c;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct optee_supplicant g_optee_supp;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: pop_entry
|
|
*
|
|
* Description:
|
|
* Pop the first request from the request queue, and create unique id.
|
|
*
|
|
* Parameters:
|
|
* num_params - Number of parameters passed.
|
|
* id - Pointer to the unique request id.
|
|
*
|
|
* Returned Value:
|
|
* A pointer to the request on success or NULL.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR struct optee_supplicant_req *pop_entry(uint32_t num_params,
|
|
FAR int *id)
|
|
{
|
|
FAR struct optee_supplicant_req *req;
|
|
|
|
if (g_optee_supp.req_id != -1)
|
|
{
|
|
/* Mixing sync/async not supported */
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (sq_empty(&g_optee_supp.reqs))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
req = (FAR struct optee_supplicant_req *)sq_remfirst(&g_optee_supp.reqs);
|
|
|
|
/* The request can't fit in the supplicant's supplied parameter buffer. */
|
|
|
|
if (num_params < req->num_params)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
*id = idr_alloc(g_optee_supp.idr, req, 0, INT32_MAX);
|
|
if (*id < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return req;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_request
|
|
*
|
|
* Description:
|
|
* Create a request with the received parameters from the OP-TEE, add it to
|
|
* the supplicant's request queue and then wait on the request's semaphore
|
|
* until it is serviced.
|
|
*
|
|
* Parameters:
|
|
* func - Requested function for the supplicant to perform
|
|
* params - Pointer pointer to parameter data.
|
|
* num_params - Number of parameters passed.
|
|
*
|
|
* Returned Value:
|
|
* TEE_SUCCESS on success or a global platform api error code on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t optee_supplicant_request(uint32_t func, uint32_t num_params,
|
|
FAR struct tee_ioctl_param *params)
|
|
{
|
|
FAR struct optee_supplicant_req req;
|
|
uint32_t ret;
|
|
|
|
/* The supplicant isn't running if the shm_idr == NULL. */
|
|
|
|
if (NULL == g_optee_supp.shm_idr)
|
|
{
|
|
return TEE_ERROR_COMMUNICATION;
|
|
}
|
|
|
|
sem_init(&req.c, 0, 0);
|
|
req.func = func;
|
|
req.num_params = num_params;
|
|
req.params = params;
|
|
|
|
nxmutex_lock(&g_optee_supp.mutex);
|
|
sq_addlast(&req.link, &g_optee_supp.reqs);
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
|
|
/* Wake supplicant receiver */
|
|
|
|
sem_post(&g_optee_supp.reqs_c);
|
|
|
|
/* Wait for completion if supplicant is running. */
|
|
|
|
while (sem_wait(&req.c) < 0)
|
|
{
|
|
}
|
|
|
|
ret = req.ret;
|
|
sem_destroy(&req.c);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_init
|
|
*
|
|
* Description:
|
|
* Initialize supplicant data.
|
|
*
|
|
* Parameters:
|
|
* shm_idr - A pointer, passed by reference, to the optee driver's shm_idr.
|
|
* The destruction of the shm_idr will be handled by
|
|
* optee_close(), so we only need to initialize it in this
|
|
* context.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void optee_supplicant_init(FAR struct idr_s **shm_idr)
|
|
{
|
|
memset(&g_optee_supp, 0, sizeof(g_optee_supp));
|
|
nxmutex_init(&g_optee_supp.mutex);
|
|
nxsem_init(&g_optee_supp.reqs_c, 0, 0);
|
|
sq_init(&g_optee_supp.reqs);
|
|
g_optee_supp.idr = idr_init();
|
|
g_optee_supp.shm_idr = idr_init();
|
|
g_optee_supp.req_id = -1;
|
|
|
|
/* Pass .shm_idr to the caller. */
|
|
|
|
*shm_idr = g_optee_supp.shm_idr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_uninit
|
|
*
|
|
* Description:
|
|
* Uninitialize supplicant data.
|
|
*
|
|
* Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void optee_supplicant_uninit(void)
|
|
{
|
|
/* The destruction of the idr_s is performed by optee_close(). */
|
|
|
|
g_optee_supp.shm_idr = NULL;
|
|
nxmutex_destroy(&g_optee_supp.mutex);
|
|
nxsem_destroy(&g_optee_supp.reqs_c);
|
|
idr_destroy(g_optee_supp.idr);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_recv
|
|
*
|
|
* Description:
|
|
* This function is invoked by an ioctl, used only by the supplicant. It
|
|
* will obtain the function to be performed and the parameters passed by
|
|
* OP-TEE.
|
|
*
|
|
* Parameters:
|
|
* func - Pointer to obtain the function to be performed by the supplicant.
|
|
* params - Pointer to the parameter data passed to supplicant.
|
|
* num_params - Pointer with the number of parameters the supplicant can
|
|
* process, and later updated with the number of parameters
|
|
* of the OP-TEE's RPC request.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, a negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int optee_supplicant_recv(FAR uint32_t *func, FAR uint32_t *num_params,
|
|
FAR struct tee_ioctl_param *params)
|
|
{
|
|
FAR struct optee_supplicant_req *req = NULL;
|
|
int id;
|
|
int n;
|
|
uint8_t num_meta = (params->attr == TEE_IOCTL_PARAM_ATTR_META);
|
|
|
|
if (0 == num_params)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (n = 0; n < *num_params; n++)
|
|
{
|
|
if (params[n].attr &&
|
|
params[n].attr != TEE_IOCTL_PARAM_ATTR_META)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
for (; ; )
|
|
{
|
|
nxmutex_lock(&g_optee_supp.mutex);
|
|
req = pop_entry(*num_params - num_meta, &id);
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
|
|
if (req)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (sem_wait(&g_optee_supp.reqs_c) < 0)
|
|
{
|
|
return -EINTR;
|
|
}
|
|
}
|
|
|
|
if (num_meta)
|
|
{
|
|
params->attr |= TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT;
|
|
params->a = id;
|
|
params->b = 0;
|
|
params->c = 0;
|
|
}
|
|
else
|
|
{
|
|
nxmutex_lock(&g_optee_supp.mutex);
|
|
g_optee_supp.req_id = id;
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
}
|
|
|
|
/* Setup parameters */
|
|
|
|
*func = req->func;
|
|
*num_params = req->num_params + num_meta;
|
|
|
|
memcpy(params + num_meta, req->params,
|
|
req->num_params * sizeof(params[0]));
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_send
|
|
*
|
|
* Description:
|
|
* This function is invoked by an ioctl, used only by the supplicant. It
|
|
* will update the parameters of the OP-TEE request with the response from
|
|
* the supplicant.
|
|
*
|
|
* Parameters:
|
|
* ret - The return value to send to OP-TEE.
|
|
* params - Contains the response parameters from nuttx.
|
|
* num_params - Number of parameters.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, a negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int optee_supplicant_send(uint32_t ret, uint32_t num_params,
|
|
FAR struct tee_ioctl_param *params)
|
|
{
|
|
FAR struct optee_supplicant_req *req;
|
|
int id;
|
|
uint8_t num_meta = 0;
|
|
const uint32_t async_attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT |
|
|
TEE_IOCTL_PARAM_ATTR_META;
|
|
|
|
nxmutex_lock(&g_optee_supp.mutex);
|
|
|
|
/* Check the parameters and obtain the request from the idr. */
|
|
|
|
if (!num_params)
|
|
{
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Async. */
|
|
|
|
if (g_optee_supp.req_id == -1)
|
|
{
|
|
if (params->attr != async_attr)
|
|
{
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
id = params->a;
|
|
num_meta = 1;
|
|
}
|
|
else
|
|
{
|
|
/* Sync. */
|
|
|
|
id = g_optee_supp.req_id;
|
|
num_meta = 0;
|
|
}
|
|
|
|
req = idr_find(g_optee_supp.idr, id);
|
|
if (!req)
|
|
{
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
return -ENOENT;
|
|
}
|
|
|
|
if ((num_params - num_meta) != req->num_params)
|
|
{
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
idr_remove(g_optee_supp.idr, id);
|
|
g_optee_supp.req_id = -1;
|
|
|
|
nxmutex_unlock(&g_optee_supp.mutex);
|
|
|
|
if (!req)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Update output and in/out parameters. */
|
|
|
|
for (size_t n = 0; n < req->num_params; n++)
|
|
{
|
|
FAR struct tee_ioctl_param *p = &req->params[n];
|
|
FAR struct tee_ioctl_param *r = ¶ms[n + num_meta];
|
|
|
|
switch (p->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK)
|
|
{
|
|
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
|
|
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
|
|
p->a = r->a;
|
|
p->b = r->b;
|
|
p->c = r->c;
|
|
break;
|
|
|
|
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
|
|
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
|
|
p->b = r->b;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
req->ret = ret;
|
|
sem_post(&req->c);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_alloc
|
|
*
|
|
* Description:
|
|
* Prepares and creates a request for userspace memory allocation that was
|
|
* requested by the OP-TEE through an RPC.
|
|
*
|
|
* Parameters:
|
|
* priv - Pointer to the driver's optee_priv_data struct.
|
|
* size - Size to allocate.
|
|
* shm - Passed by reference pointer to shared memory. On success it will
|
|
* be updated with the shared memory the supplicant allocated.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, a negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int optee_supplicant_alloc(FAR struct optee_priv_data *priv,
|
|
size_t size, FAR struct optee_shm **shm)
|
|
{
|
|
uint32_t ret;
|
|
struct tee_ioctl_param param;
|
|
|
|
param.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT;
|
|
param.a = OPTEE_MSG_RPC_SHM_TYPE_APPL;
|
|
param.b = size;
|
|
param.c = 0;
|
|
|
|
ret = optee_supplicant_request(OPTEE_MSG_RPC_CMD_SHM_ALLOC, 1, ¶m);
|
|
if (ret)
|
|
{
|
|
return optee_convert_to_errno(ret);
|
|
}
|
|
|
|
if (NULL == g_optee_supp.shm_idr)
|
|
{
|
|
return -ECOMM;
|
|
}
|
|
|
|
*shm = idr_find(g_optee_supp.shm_idr, param.c);
|
|
|
|
if (NULL == *shm)
|
|
{
|
|
return -ENOENT;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_free
|
|
*
|
|
* Description:
|
|
* Handles freeing of shared memory allocated by the TEE supplicant.
|
|
*
|
|
* Parameters:
|
|
* shm_id - The id of the shared memory to be freed.
|
|
*
|
|
* Returned Value:
|
|
* TEE_SUCCESS on success or a global platform api error code on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
uint32_t optee_supplicant_free(int32_t shm_id)
|
|
{
|
|
struct tee_ioctl_param param;
|
|
uint32_t ret;
|
|
|
|
param.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT;
|
|
param.a = OPTEE_MSG_RPC_SHM_TYPE_APPL;
|
|
param.b = shm_id;
|
|
param.c = 0;
|
|
|
|
ret = optee_supplicant_request(OPTEE_MSG_RPC_CMD_SHM_FREE, 1, ¶m);
|
|
|
|
if (ret != TEE_SUCCESS)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (NULL == g_optee_supp.shm_idr)
|
|
{
|
|
ret = TEE_ERROR_COMMUNICATION;
|
|
return ret;
|
|
}
|
|
|
|
idr_remove(g_optee_supp.shm_idr, shm_id);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_cmd
|
|
*
|
|
* Description:
|
|
* Copy the parameters from OP-TEE's message and then create a request to
|
|
* supplicant to service this RPC request.
|
|
*
|
|
* Parameters:
|
|
* priv - Pointer to the driver's optee_priv_data struct.
|
|
* arg - Pointer to the RPC message argument, located in a shared page, by
|
|
* by the secure world. A copy of this message will be sent to the
|
|
* supplicant process that runs in userspace for further processing.
|
|
*
|
|
* Returned Value:
|
|
* None. Result codes are written into arg->ret.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void optee_supplicant_cmd(FAR struct optee_priv_data *priv,
|
|
FAR struct optee_msg_arg *arg)
|
|
{
|
|
FAR struct tee_ioctl_param *params;
|
|
int ret = 0;
|
|
|
|
arg->ret_origin = TEE_ORIGIN_COMMS;
|
|
|
|
params = kmm_zalloc(TEE_IOCTL_PARAM_SIZE(arg->num_params));
|
|
if (!params)
|
|
{
|
|
arg->ret = TEE_ERROR_OUT_OF_MEMORY;
|
|
return;
|
|
}
|
|
|
|
if ((ret = optee_from_msg_param(params, arg->num_params, arg->params))
|
|
!= 0)
|
|
{
|
|
arg->ret = optee_convert_from_errno(ret);
|
|
goto out;
|
|
}
|
|
|
|
arg->ret = optee_supplicant_request(arg->cmd, arg->num_params, params);
|
|
if (arg->ret != TEE_SUCCESS)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
ret = optee_to_msg_param(priv, arg->params, arg->num_params, params);
|
|
arg->ret = optee_convert_from_errno(ret);
|
|
|
|
out:
|
|
kmm_free(params);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: optee_supplicant_search_shm
|
|
*
|
|
* Description:
|
|
* Search the supplicant's idr_s for a specific id and return a pointer to
|
|
* that shared memory if found.
|
|
*
|
|
* Parameters:
|
|
* id - Shared memory id to search for, in the supplicant's idr_s.
|
|
*
|
|
* Returned Value:
|
|
* Pointer to the shm with the specified id or NULL.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct optee_shm *optee_supplicant_search_shm(uint32_t id)
|
|
{
|
|
return idr_find(g_optee_supp.shm_idr, id);
|
|
}
|