root/drivers/tee/optee/call.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2015-2021, 2023 Linaro Limited
 */
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/tee_core.h>
#include <linux/types.h>
#include "optee_private.h"

#define MAX_ARG_PARAM_COUNT     6

/*
 * How much memory we allocate for each entry. This doesn't have to be a
 * single page, but it makes sense to keep at least keep it as multiples of
 * the page size.
 */
#define SHM_ENTRY_SIZE          PAGE_SIZE

/*
 * We need to have a compile time constant to be able to determine the
 * maximum needed size of the bit field.
 */
#define MIN_ARG_SIZE            OPTEE_MSG_GET_ARG_SIZE(MAX_ARG_PARAM_COUNT)
#define MAX_ARG_COUNT_PER_ENTRY (SHM_ENTRY_SIZE / MIN_ARG_SIZE)

/*
 * Shared memory for argument structs are cached here. The number of
 * arguments structs that can fit is determined at runtime depending on the
 * needed RPC parameter count reported by secure world
 * (optee->rpc_param_count).
 */
struct optee_shm_arg_entry {
        struct list_head list_node;
        struct tee_shm *shm;
        DECLARE_BITMAP(map, MAX_ARG_COUNT_PER_ENTRY);
};

void optee_cq_init(struct optee_call_queue *cq, int thread_count)
{
        mutex_init(&cq->mutex);
        INIT_LIST_HEAD(&cq->waiters);

        /*
         * If cq->total_thread_count is 0 then we're not trying to keep
         * track of how many free threads we have, instead we're relying on
         * the secure world to tell us when we're out of thread and have to
         * wait for another thread to become available.
         */
        cq->total_thread_count = thread_count;
        cq->free_thread_count = thread_count;
}

void optee_cq_wait_init(struct optee_call_queue *cq,
                        struct optee_call_waiter *w, bool sys_thread)
{
        unsigned int free_thread_threshold;
        bool need_wait = false;

        memset(w, 0, sizeof(*w));

        /*
         * We're preparing to make a call to secure world. In case we can't
         * allocate a thread in secure world we'll end up waiting in
         * optee_cq_wait_for_completion().
         *
         * Normally if there's no contention in secure world the call will
         * complete and we can cleanup directly with optee_cq_wait_final().
         */
        mutex_lock(&cq->mutex);

        /*
         * We add ourselves to the queue, but we don't wait. This
         * guarantees that we don't lose a completion if secure world
         * returns busy and another thread just exited and try to complete
         * someone.
         */
        init_completion(&w->c);
        list_add_tail(&w->list_node, &cq->waiters);
        w->sys_thread = sys_thread;

        if (cq->total_thread_count) {
                if (sys_thread || !cq->sys_thread_req_count)
                        free_thread_threshold = 0;
                else
                        free_thread_threshold = 1;

                if (cq->free_thread_count > free_thread_threshold)
                        cq->free_thread_count--;
                else
                        need_wait = true;
        }

        mutex_unlock(&cq->mutex);

        while (need_wait) {
                optee_cq_wait_for_completion(cq, w);
                mutex_lock(&cq->mutex);

                if (sys_thread || !cq->sys_thread_req_count)
                        free_thread_threshold = 0;
                else
                        free_thread_threshold = 1;

                if (cq->free_thread_count > free_thread_threshold) {
                        cq->free_thread_count--;
                        need_wait = false;
                }

                mutex_unlock(&cq->mutex);
        }
}

void optee_cq_wait_for_completion(struct optee_call_queue *cq,
                                  struct optee_call_waiter *w)
{
        wait_for_completion(&w->c);

        mutex_lock(&cq->mutex);

        /* Move to end of list to get out of the way for other waiters */
        list_del(&w->list_node);
        reinit_completion(&w->c);
        list_add_tail(&w->list_node, &cq->waiters);

        mutex_unlock(&cq->mutex);
}

static void optee_cq_complete_one(struct optee_call_queue *cq)
{
        struct optee_call_waiter *w;

        /* Wake a waiting system session if any, prior to a normal session */
        list_for_each_entry(w, &cq->waiters, list_node) {
                if (w->sys_thread && !completion_done(&w->c)) {
                        complete(&w->c);
                        return;
                }
        }

        list_for_each_entry(w, &cq->waiters, list_node) {
                if (!completion_done(&w->c)) {
                        complete(&w->c);
                        break;
                }
        }
}

void optee_cq_wait_final(struct optee_call_queue *cq,
                         struct optee_call_waiter *w)
{
        /*
         * We're done with the call to secure world. The thread in secure
         * world that was used for this call is now available for some
         * other task to use.
         */
        mutex_lock(&cq->mutex);

        /* Get out of the list */
        list_del(&w->list_node);

        cq->free_thread_count++;

        /* Wake up one eventual waiting task */
        optee_cq_complete_one(cq);

        /*
         * If we're completed we've got a completion from another task that
         * was just done with its call to secure world. Since yet another
         * thread now is available in secure world wake up another eventual
         * waiting task.
         */
        if (completion_done(&w->c))
                optee_cq_complete_one(cq);

        mutex_unlock(&cq->mutex);
}

/* Count registered system sessions to reserved a system thread or not */
static bool optee_cq_incr_sys_thread_count(struct optee_call_queue *cq)
{
        if (cq->total_thread_count <= 1)
                return false;

        mutex_lock(&cq->mutex);
        cq->sys_thread_req_count++;
        mutex_unlock(&cq->mutex);

        return true;
}

static void optee_cq_decr_sys_thread_count(struct optee_call_queue *cq)
{
        mutex_lock(&cq->mutex);
        cq->sys_thread_req_count--;
        /* If there's someone waiting, let it resume */
        optee_cq_complete_one(cq);
        mutex_unlock(&cq->mutex);
}

/* Requires the filpstate mutex to be held */
static struct optee_session *find_session(struct optee_context_data *ctxdata,
                                          u32 session_id)
{
        struct optee_session *sess;

        list_for_each_entry(sess, &ctxdata->sess_list, list_node)
                if (sess->session_id == session_id)
                        return sess;

        return NULL;
}

void optee_shm_arg_cache_init(struct optee *optee, u32 flags)
{
        INIT_LIST_HEAD(&optee->shm_arg_cache.shm_args);
        mutex_init(&optee->shm_arg_cache.mutex);
        optee->shm_arg_cache.flags = flags;
}

void optee_shm_arg_cache_uninit(struct optee *optee)
{
        struct list_head *head = &optee->shm_arg_cache.shm_args;
        struct optee_shm_arg_entry *entry;

        mutex_destroy(&optee->shm_arg_cache.mutex);
        while (!list_empty(head)) {
                entry = list_first_entry(head, struct optee_shm_arg_entry,
                                         list_node);
                list_del(&entry->list_node);
                if (find_first_bit(entry->map, MAX_ARG_COUNT_PER_ENTRY) !=
                     MAX_ARG_COUNT_PER_ENTRY) {
                        pr_err("Freeing non-free entry\n");
                }
                tee_shm_free(entry->shm);
                kfree(entry);
        }
}

size_t optee_msg_arg_size(size_t rpc_param_count)
{
        size_t sz = OPTEE_MSG_GET_ARG_SIZE(MAX_ARG_PARAM_COUNT);

        if (rpc_param_count)
                sz += OPTEE_MSG_GET_ARG_SIZE(rpc_param_count);

        return sz;
}

/**
 * optee_get_msg_arg() - Provide shared memory for argument struct
 * @ctx:        Caller TEE context
 * @num_params: Number of parameter to store
 * @entry_ret:  Entry pointer, needed when freeing the buffer
 * @shm_ret:    Shared memory buffer
 * @offs_ret:   Offset of argument strut in shared memory buffer
 *
 * @returns a pointer to the argument struct in memory, else an ERR_PTR
 */
struct optee_msg_arg *optee_get_msg_arg(struct tee_context *ctx,
                                        size_t num_params,
                                        struct optee_shm_arg_entry **entry_ret,
                                        struct tee_shm **shm_ret,
                                        u_int *offs_ret)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        size_t sz = optee_msg_arg_size(optee->rpc_param_count);
        struct optee_shm_arg_entry *entry;
        struct optee_msg_arg *ma;
        size_t args_per_entry;
        u_long bit;
        u_int offs;
        void *res;

        if (num_params > MAX_ARG_PARAM_COUNT)
                return ERR_PTR(-EINVAL);

        if (optee->shm_arg_cache.flags & OPTEE_SHM_ARG_SHARED)
                args_per_entry = SHM_ENTRY_SIZE / sz;
        else
                args_per_entry = 1;

        mutex_lock(&optee->shm_arg_cache.mutex);
        list_for_each_entry(entry, &optee->shm_arg_cache.shm_args, list_node) {
                bit = find_first_zero_bit(entry->map, MAX_ARG_COUNT_PER_ENTRY);
                if (bit < args_per_entry)
                        goto have_entry;
        }

        /*
         * No entry was found, let's allocate a new.
         */
        entry = kzalloc_obj(*entry);
        if (!entry) {
                res = ERR_PTR(-ENOMEM);
                goto out;
        }

        if (optee->shm_arg_cache.flags & OPTEE_SHM_ARG_ALLOC_PRIV)
                res = tee_shm_alloc_priv_buf(ctx, SHM_ENTRY_SIZE);
        else
                res = tee_shm_alloc_kernel_buf(ctx, SHM_ENTRY_SIZE);

        if (IS_ERR(res)) {
                kfree(entry);
                goto out;
        }
        entry->shm = res;
        list_add(&entry->list_node, &optee->shm_arg_cache.shm_args);
        bit = 0;

have_entry:
        offs = bit * sz;
        res = tee_shm_get_va(entry->shm, offs);
        if (IS_ERR(res))
                goto out;
        ma = res;
        set_bit(bit, entry->map);
        memset(ma, 0, sz);
        ma->num_params = num_params;
        *entry_ret = entry;
        *shm_ret = entry->shm;
        *offs_ret = offs;
out:
        mutex_unlock(&optee->shm_arg_cache.mutex);
        return res;
}

/**
 * optee_free_msg_arg() - Free previsouly obtained shared memory
 * @ctx:        Caller TEE context
 * @entry:      Pointer returned when the shared memory was obtained
 * @offs:       Offset of shared memory buffer to free
 *
 * This function frees the shared memory obtained with optee_get_msg_arg().
 */
void optee_free_msg_arg(struct tee_context *ctx,
                        struct optee_shm_arg_entry *entry, u_int offs)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        size_t sz = optee_msg_arg_size(optee->rpc_param_count);
        u_long bit;

        if (offs > SHM_ENTRY_SIZE || offs % sz) {
                pr_err("Invalid offs %u\n", offs);
                return;
        }
        bit = offs / sz;

        mutex_lock(&optee->shm_arg_cache.mutex);

        if (!test_bit(bit, entry->map))
                pr_err("Bit pos %lu is already free\n", bit);
        clear_bit(bit, entry->map);

        mutex_unlock(&optee->shm_arg_cache.mutex);
}

int optee_open_session(struct tee_context *ctx,
                       struct tee_ioctl_open_session_arg *arg,
                       struct tee_param *param)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        struct optee_context_data *ctxdata = ctx->data;
        struct optee_shm_arg_entry *entry;
        struct tee_shm *shm;
        struct optee_msg_arg *msg_arg;
        struct optee_session *sess = NULL;
        uuid_t client_uuid;
        u_int offs;
        int rc;

        /* +2 for the meta parameters added below */
        msg_arg = optee_get_msg_arg(ctx, arg->num_params + 2,
                                    &entry, &shm, &offs);
        if (IS_ERR(msg_arg))
                return PTR_ERR(msg_arg);

        msg_arg->cmd = OPTEE_MSG_CMD_OPEN_SESSION;
        msg_arg->cancel_id = arg->cancel_id;

        /*
         * Initialize and add the meta parameters needed when opening a
         * session.
         */
        msg_arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT |
                                  OPTEE_MSG_ATTR_META;
        msg_arg->params[1].attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT |
                                  OPTEE_MSG_ATTR_META;
        memcpy(&msg_arg->params[0].u.value, arg->uuid, sizeof(arg->uuid));
        msg_arg->params[1].u.value.c = arg->clnt_login;

        rc = tee_session_calc_client_uuid(&client_uuid, arg->clnt_login,
                                          arg->clnt_uuid);
        if (rc)
                goto out;
        export_uuid(msg_arg->params[1].u.octets, &client_uuid);

        rc = optee->ops->to_msg_param(optee, msg_arg->params + 2,
                                      arg->num_params, param);
        if (rc)
                goto out;

        sess = kzalloc_obj(*sess);
        if (!sess) {
                rc = -ENOMEM;
                goto out;
        }

        if (optee->ops->do_call_with_arg(ctx, shm, offs,
                                         sess->use_sys_thread)) {
                msg_arg->ret = TEEC_ERROR_COMMUNICATION;
                msg_arg->ret_origin = TEEC_ORIGIN_COMMS;
        }

        if (msg_arg->ret == TEEC_SUCCESS) {
                /* A new session has been created, add it to the list. */
                sess->session_id = msg_arg->session;
                mutex_lock(&ctxdata->mutex);
                list_add(&sess->list_node, &ctxdata->sess_list);
                mutex_unlock(&ctxdata->mutex);
        } else {
                kfree(sess);
        }

        if (optee->ops->from_msg_param(optee, param, arg->num_params,
                                       msg_arg->params + 2)) {
                arg->ret = TEEC_ERROR_COMMUNICATION;
                arg->ret_origin = TEEC_ORIGIN_COMMS;
                /* Close session again to avoid leakage */
                optee_close_session(ctx, msg_arg->session);
        } else {
                arg->session = msg_arg->session;
                arg->ret = msg_arg->ret;
                arg->ret_origin = msg_arg->ret_origin;
        }
out:
        optee_free_msg_arg(ctx, entry, offs);

        return rc;
}

int optee_system_session(struct tee_context *ctx, u32 session)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        struct optee_context_data *ctxdata = ctx->data;
        struct optee_session *sess;
        int rc = -EINVAL;

        mutex_lock(&ctxdata->mutex);

        sess = find_session(ctxdata, session);
        if (sess && (sess->use_sys_thread ||
                     optee_cq_incr_sys_thread_count(&optee->call_queue))) {
                sess->use_sys_thread = true;
                rc = 0;
        }

        mutex_unlock(&ctxdata->mutex);

        return rc;
}

int optee_close_session_helper(struct tee_context *ctx, u32 session,
                               bool system_thread)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        struct optee_shm_arg_entry *entry;
        struct optee_msg_arg *msg_arg;
        struct tee_shm *shm;
        u_int offs;

        msg_arg = optee_get_msg_arg(ctx, 0, &entry, &shm, &offs);
        if (IS_ERR(msg_arg))
                return PTR_ERR(msg_arg);

        msg_arg->cmd = OPTEE_MSG_CMD_CLOSE_SESSION;
        msg_arg->session = session;
        optee->ops->do_call_with_arg(ctx, shm, offs, system_thread);

        optee_free_msg_arg(ctx, entry, offs);

        if (system_thread)
                optee_cq_decr_sys_thread_count(&optee->call_queue);

        return 0;
}

int optee_close_session(struct tee_context *ctx, u32 session)
{
        struct optee_context_data *ctxdata = ctx->data;
        struct optee_session *sess;
        bool system_thread;

        /* Check that the session is valid and remove it from the list */
        mutex_lock(&ctxdata->mutex);
        sess = find_session(ctxdata, session);
        if (sess)
                list_del(&sess->list_node);
        mutex_unlock(&ctxdata->mutex);
        if (!sess)
                return -EINVAL;
        system_thread = sess->use_sys_thread;
        kfree(sess);

        return optee_close_session_helper(ctx, session, system_thread);
}

int optee_invoke_func(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg,
                      struct tee_param *param)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        struct optee_context_data *ctxdata = ctx->data;
        struct optee_shm_arg_entry *entry;
        struct optee_msg_arg *msg_arg;
        struct optee_session *sess;
        struct tee_shm *shm;
        bool system_thread;
        u_int offs;
        int rc;

        /* Check that the session is valid */
        mutex_lock(&ctxdata->mutex);
        sess = find_session(ctxdata, arg->session);
        if (sess)
                system_thread = sess->use_sys_thread;
        mutex_unlock(&ctxdata->mutex);
        if (!sess)
                return -EINVAL;

        msg_arg = optee_get_msg_arg(ctx, arg->num_params,
                                    &entry, &shm, &offs);
        if (IS_ERR(msg_arg))
                return PTR_ERR(msg_arg);
        msg_arg->cmd = OPTEE_MSG_CMD_INVOKE_COMMAND;
        msg_arg->func = arg->func;
        msg_arg->session = arg->session;
        msg_arg->cancel_id = arg->cancel_id;

        rc = optee->ops->to_msg_param(optee, msg_arg->params, arg->num_params,
                                      param);
        if (rc)
                goto out;

        if (optee->ops->do_call_with_arg(ctx, shm, offs, system_thread)) {
                msg_arg->ret = TEEC_ERROR_COMMUNICATION;
                msg_arg->ret_origin = TEEC_ORIGIN_COMMS;
        }

        if (optee->ops->from_msg_param(optee, param, arg->num_params,
                                       msg_arg->params)) {
                msg_arg->ret = TEEC_ERROR_COMMUNICATION;
                msg_arg->ret_origin = TEEC_ORIGIN_COMMS;
        }

        arg->ret = msg_arg->ret;
        arg->ret_origin = msg_arg->ret_origin;
out:
        optee_free_msg_arg(ctx, entry, offs);
        return rc;
}

int optee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        struct optee_context_data *ctxdata = ctx->data;
        struct optee_shm_arg_entry *entry;
        struct optee_msg_arg *msg_arg;
        struct optee_session *sess;
        bool system_thread;
        struct tee_shm *shm;
        u_int offs;

        /* Check that the session is valid */
        mutex_lock(&ctxdata->mutex);
        sess = find_session(ctxdata, session);
        if (sess)
                system_thread = sess->use_sys_thread;
        mutex_unlock(&ctxdata->mutex);
        if (!sess)
                return -EINVAL;

        msg_arg = optee_get_msg_arg(ctx, 0, &entry, &shm, &offs);
        if (IS_ERR(msg_arg))
                return PTR_ERR(msg_arg);

        msg_arg->cmd = OPTEE_MSG_CMD_CANCEL;
        msg_arg->session = session;
        msg_arg->cancel_id = cancel_id;
        optee->ops->do_call_with_arg(ctx, shm, offs, system_thread);

        optee_free_msg_arg(ctx, entry, offs);
        return 0;
}

static bool is_normal_memory(pgprot_t p)
{
#if defined(CONFIG_ARM)
        return (((pgprot_val(p) & L_PTE_MT_MASK) == L_PTE_MT_WRITEALLOC) ||
                ((pgprot_val(p) & L_PTE_MT_MASK) == L_PTE_MT_WRITEBACK));
#elif defined(CONFIG_ARM64)
        return (pgprot_val(p) & PTE_ATTRINDX_MASK) == PTE_ATTRINDX(MT_NORMAL);
#else
#error "Unsupported architecture"
#endif
}

static int __check_mem_type(struct mm_struct *mm, unsigned long start,
                                unsigned long end)
{
        struct vm_area_struct *vma;
        VMA_ITERATOR(vmi, mm, start);

        for_each_vma_range(vmi, vma, end) {
                if (!is_normal_memory(vma->vm_page_prot))
                        return -EINVAL;
        }

        return 0;
}

int optee_check_mem_type(unsigned long start, size_t num_pages)
{
        struct mm_struct *mm = current->mm;
        int rc;

        /*
         * Allow kernel address to register with OP-TEE as kernel
         * pages are configured as normal memory only.
         */
        if (virt_addr_valid((void *)start) || is_vmalloc_addr((void *)start))
                return 0;

        mmap_read_lock(mm);
        rc = __check_mem_type(mm, start, start + num_pages * PAGE_SIZE);
        mmap_read_unlock(mm);

        return rc;
}

static int simple_call_with_arg(struct tee_context *ctx, u32 cmd)
{
        struct optee *optee = tee_get_drvdata(ctx->teedev);
        struct optee_shm_arg_entry *entry;
        struct optee_msg_arg *msg_arg;
        struct tee_shm *shm;
        u_int offs;

        msg_arg = optee_get_msg_arg(ctx, 0, &entry, &shm, &offs);
        if (IS_ERR(msg_arg))
                return PTR_ERR(msg_arg);

        msg_arg->cmd = cmd;
        optee->ops->do_call_with_arg(ctx, shm, offs, false);

        optee_free_msg_arg(ctx, entry, offs);
        return 0;
}

int optee_do_bottom_half(struct tee_context *ctx)
{
        return simple_call_with_arg(ctx, OPTEE_MSG_CMD_DO_BOTTOM_HALF);
}

int optee_stop_async_notif(struct tee_context *ctx)
{
        return simple_call_with_arg(ctx, OPTEE_MSG_CMD_STOP_ASYNC_NOTIF);
}