Provide ioctl to expose support to invoke a TEE object to userspace and implementing a callback server to handle TEE object invokes. Signed-off-by: Amirreza Zarrabi <quic_azarrabi@xxxxxxxxxxx> --- drivers/firmware/qcom/Kconfig | 12 + drivers/firmware/qcom/qcom_object_invoke/Makefile | 3 + .../qcom_object_invoke/xts/object_invoke_uapi.c | 1231 ++++++++++++++++++++ include/uapi/misc/qcom_tee.h | 117 ++ 4 files changed, 1363 insertions(+) diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index f16fb7997595..6592f79d3b70 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -108,4 +108,16 @@ config QCOM_OBJECT_INVOKE_MEM_OBJECT Select Y here Enable support for memory object. +config QCOM_OBJECT_INVOKE + bool "Add support for userspace to access TEE" + select QCOM_OBJECT_INVOKE_CORE + select QCOM_OBJECT_INVOKE_MEM_OBJECT + help + This provides an interface to access TEE from userspace. It creates two + char devices /dev/tee and /dev/tee-ree. The /dev/tee is used to obtain + access to the root client env object. The /dev/tee-ree is used to start a + callback server. + + Select Y here to provide access to TEE. + endmenu diff --git a/drivers/firmware/qcom/qcom_object_invoke/Makefile b/drivers/firmware/qcom/qcom_object_invoke/Makefile index 1f7d43fa38db..9c2350fff6b7 100644 --- a/drivers/firmware/qcom/qcom_object_invoke/Makefile +++ b/drivers/firmware/qcom/qcom_object_invoke/Makefile @@ -7,3 +7,6 @@ object-invoke-core-objs := qcom_scm_invoke.o release_wq.o async.o core.o obj-$(CONFIG_QCOM_OBJECT_INVOKE_MEM_OBJECT) += mem-object.o mem-object-objs := xts/mem_object.o + +obj-$(CONFIG_QCOM_OBJECT_INVOKE) += object-invoke-uapi.o +object-invoke-uapi-objs := xts/object_invoke_uapi.o diff --git a/drivers/firmware/qcom/qcom_object_invoke/xts/object_invoke_uapi.c b/drivers/firmware/qcom/qcom_object_invoke/xts/object_invoke_uapi.c new file mode 100644 index 000000000000..b6d2473e183c --- /dev/null +++ b/drivers/firmware/qcom/qcom_object_invoke/xts/object_invoke_uapi.c @@ -0,0 +1,1231 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "qcom-object-invoke-uapi: %s: " fmt, __func__ + +#include <linux/module.h> +#include <linux/file.h> +#include <linux/dma-buf.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/anon_inodes.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/mod_devicetable.h> + +#include <linux/firmware/qcom/qcom_object_invoke.h> + +/* Mutex to protect userspace processes. */ +static DEFINE_MUTEX(si_mutex); + +static const struct file_operations qtee_fops; +static const struct file_operations server_fops; + +struct server_info { + struct kref refcount; + + /* List of transactions pending for service. */ + struct list_head cb_tx_list; + + int id, dead; + + /* Queue of threads waiting for a new transaction. */ + wait_queue_head_t server_threads; +}; + +/* Dispatcher is called with context ID [10 .. n] from qcom_object_invoke_core.c. + * Any ID below 10 is available to call dispatcher internally. + * Here, CONTEXT_ID_ANY is used to state that it is an async call, e.g. release. + */ +#define CONTEXT_ID_ANY 0 + +/* A transaction made to usespace server host an object. */ +struct cb_txn { + struct kref refcount; + struct list_head node; + struct completion completion; + + /* ''Object Invocation'' */ + + struct qcom_tee_arg *args; /* Arguments for the requested operation. */ + int errno; /* Result of the operation. */ + + enum state { + XST_NEW = 0, /* New transaction. */ + XST_PENDING = 1, /* Waiting for server. */ + XST_PROCESSING = 2, /* Being processed by server. */ + XST_PROCESSED = 3, /* Done. */ + XST_TIMEDOUT = 4, + } processing; + + /* ''Object Invocation'' as seen by userspace. */ + + struct qcom_tee_cb_arg *uargs; + size_t uargs_size; +}; + +/* 'struct cb_object' is a userspace object. */ +struct cb_object { + struct qcom_tee_object object; + + /* If set, we send release request to userspace. */ + int notify_on_release; + + /* 'id' + 'server_info' combo that represents user object.*/ + u64 id; + struct server_info *si; +}; + +static struct qcom_tee_object_operations cbo_ops; + +#define to_cb_object(o) container_of((o), struct cb_object, object) + +static int is_cb_object(struct qcom_tee_object *object) +{ + return (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT) && + (object->ops == &cbo_ops); +} + +static int fd_alloc(const char *name, const struct file_operations *fops, void *private) +{ + int fd; + struct file *file; + + fd = get_unused_fd_flags(O_RDWR); + if (fd < 0) + return fd; + + file = anon_inode_getfile(name, fops, private, O_RDWR); + if (!IS_ERR(file)) { + fd_install(fd, file); + + return fd; + } + + put_unused_fd(fd); + + return PTR_ERR(file); +} + +static struct file *get_file_of_type(int fd, const struct file_operations *fop) +{ + struct file *filp; + + filp = fget(fd); + if (!filp) + return NULL; + + if (filp->f_op == fop) + return filp; + + fput(filp); + + return NULL; +} + +static struct cb_object *cb_object_alloc_for_param(struct qcom_tee_param *param) +{ + struct file *filp; + struct cb_object *cb_object; + + filp = get_file_of_type(param->object.host_id, &server_fops); + if (!filp) + return ERR_PTR(-EBADF); + + cb_object = kzalloc(sizeof(*cb_object), GFP_KERNEL); + if (cb_object) { + kref_get(&cb_object->si->refcount); + cb_object->notify_on_release = 1; /* Default: notify. */ + cb_object->id = param->object.id; + cb_object->si = filp->private_data; + + } else { + cb_object = ERR_PTR(-ENOMEM); + } + + fput(filp); + + return cb_object; +} + +/* QCOM_TEE_OBJECT to/from PARAM. */ + +/* This declaration should be removed, see comments in get_qcom_tee_object_from_param. */ +struct qcom_tee_object *qcom_tee_mem_object_init(struct dma_buf *dma_buf, + void (*release)(void *), void *private); + +/* get_qcom_tee_object_from_param - converts a param to instance of qcom_tee_object. + * It calls get_qcom_tee_object before returning (i.e. ref == 2) for all objects + * except QCOM_TEE_OBJECT_TYPE_USER: One reference for TEE and one for driver itself. + */ +static int get_qcom_tee_object_from_param(struct qcom_tee_param *param, struct qcom_tee_arg *arg) +{ + int ret = 0; + struct qcom_tee_object *object; + + if (param->attr == QCOM_TEE_OBJECT) { + if (QCOM_TEE_PARAM_OBJECT_USER(param)) { + struct cb_object *cb_object; + + cb_object = cb_object_alloc_for_param(param); + if (!IS_ERR(cb_object)) { + object = &cb_object->object; + + init_qcom_tee_object_user(object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT, + &cbo_ops, "cbo"); + + get_qcom_tee_object(object); + } else { + ret = PTR_ERR(cb_object); + } + + } else if (QCOM_TEE_PARAM_OBJECT_KERNEL(param)) { + struct dma_buf *dma_buf; + + /* param->object.host_id == QCOM_TEE_MEMORY_OBJECT. */ + + /* TODO. For now, we only have memory object that is hosted in kernel + * so keep it simple. We should move this conversation to the code + * implements the object using @param_to_object callback. + */ + + dma_buf = dma_buf_get(param->object.id); + if (!IS_ERR(dma_buf)) { + object = qcom_tee_mem_object_init(dma_buf, NULL, NULL); + if (!object) + ret = -EINVAL; + + get_qcom_tee_object(object); + + /* qcom_tee_mem_object_init calls dma_buf_get internally. */ + dma_buf_put(dma_buf); + } else { + ret = -EINVAL; + } + + } else { /* QCOM_TEE_PARAM_OBJECT_SECURE(param). */ + struct file *filp; + + filp = get_file_of_type(param->object.id, &qtee_fops); + if (filp) { + object = filp->private_data; + + /* We put 'filp' while keeping the instance of object. */ + get_qcom_tee_object(object); + + fput(filp); + } else { + ret = -EINVAL; + } + } + + } else if (param->attr == QCOM_TEE_OBJECT_NULL) { + object = NULL_QCOM_TEE_OBJECT; + + } else { /* param->attr == QCOM_TEE_BUFFER. */ + ret = -EINVAL; + } + + if (ret) + object = NULL_QCOM_TEE_OBJECT; + + arg->o = object; + + return ret; +} + +/* This declaration should be removed, see comments in get_param_from_qcom_tee_object. */ +struct dma_buf *qcom_tee_mem_object_to_dma_buf(struct qcom_tee_object *object); + +/* get_param_from_qcom_tee_object - converts object to param. + * On SUCCESS, it calls put_qcom_tee_object before returning for all objects except + * QCOM_TEE_OBJECT_TYPE_USER. get_param_from_qcom_tee_object only initializes the + * object and attr fields. + */ +static int get_param_from_qcom_tee_object(struct qcom_tee_object *object, + struct qcom_tee_param *param, struct server_info **si) +{ + int ret = 0; + + if (si) + *si = NULL; + + switch (typeof_qcom_tee_object(object)) { + case QCOM_TEE_OBJECT_TYPE_NULL: + param->attr = QCOM_TEE_OBJECT_NULL; + + break; + case QCOM_TEE_OBJECT_TYPE_CB_OBJECT: + param->attr = QCOM_TEE_OBJECT; + + if (is_cb_object(object)) { + struct cb_object *cb_object = to_cb_object(object); + + param->object.id = cb_object->id; + param->object.host_id = cb_object->si->id; + + if (si) + *si = cb_object->si; + + put_qcom_tee_object(object); + + } else { + struct dma_buf *dma_buf = qcom_tee_mem_object_to_dma_buf(object); + + /* TODO. For now, we only have memory object that is hosted in kernel + * so keep it simple. We should move this conversation to the code + * implements the object using @object_to_param callback. + */ + + get_dma_buf(dma_buf); + param->object.id = dma_buf_fd(dma_buf, O_CLOEXEC); + if (param->object.id < 0) { + dma_buf_put(dma_buf); + + ret = -EBADF; + } else { + param->object.host_id = QCOM_TEE_MEMORY_OBJECT; + + put_qcom_tee_object(object); + } + } + + break; + case QCOM_TEE_OBJECT_TYPE_USER: + param->attr = QCOM_TEE_OBJECT; + param->object.host_id = QCOM_TEE_OBJECT_SECURE; + param->object.id = fd_alloc(qcom_tee_object_name(object), &qtee_fops, object); + if (param->object.id < 0) + ret = -EBADF; + + /* On SUCCESS, do not call put_qcom_tee_object. + * refcount is used by file's private_data. + */ + + break; + case QCOM_TEE_OBJECT_TYPE_ROOT: + default: + ret = -EBADF; + + break; + } + + if (ret) + param->attr = QCOM_TEE_OBJECT_NULL; + + return ret; +} + +/* Marshaling API. */ +/* marshal_in_req Prepare input buffer for sending to TEE. + * marshal_out_req Parse TEE response in input buffer. + * marshal_in_cb_req Parse TEE request from output buffer. + * marshal_out_cb_req Update output buffer with response for TEE request. + * + * marshal_in_req and marshal_out_req are used in direct invocation path. + * marshal_in_cb_req and marshal_out_cb_req are used for TEE request. + */ + +static void marshal_in_req_cleanup(struct qcom_tee_arg u[], int notify) +{ + int i; + struct qcom_tee_object *object; + + for (i = 0; u[i].type; i++) { + switch (u[i].type) { + case QCOM_TEE_ARG_TYPE_IO: + object = u[i].o; + + if (is_cb_object(object)) + to_cb_object(object)->notify_on_release = notify; + + /* For object of type QCOM_TEE_OBJECT_TYPE_USER, + * get_qcom_tee_object_from_param does not call get_qcom_tee_object + * before returning (i.e. ref == 1). Replace it with + * NULL_QCOM_TEE_OBJECT as after put_qcom_tee_object, + * u[i].o is invalid. + */ + + else if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_USER) + u[i].o = NULL_QCOM_TEE_OBJECT; + + put_qcom_tee_object(object); + + break; + case QCOM_TEE_ARG_TYPE_IB: + case QCOM_TEE_ARG_TYPE_OB: + case QCOM_TEE_ARG_TYPE_OO: + default: + + break; + } + } +} + +static int marshal_in_req(struct qcom_tee_arg u[], struct qcom_tee_param *params, int num_params) +{ + int i; + + /* Assume 'u' already cleared. */ + + for (i = 0; i < num_params; i++) { + if (params[i].attr == QCOM_TEE_BUFFER) { + if (params[i].direction) + u[i].type = QCOM_TEE_ARG_TYPE_IB; + else + u[i].type = QCOM_TEE_ARG_TYPE_OB; + + u[i].flags = QCOM_TEE_ARG_FLAGS_UADDR; + u[i].b.uaddr = u64_to_user_ptr(params[i].buffer.addr); + u[i].b.size = params[i].buffer.len; + + } else { /* QCOM_TEE_OBJECT || QCOM_TEE_OBJECT_NULL */ + if (params[i].direction) { + if (get_qcom_tee_object_from_param(¶ms[i], &u[i])) + goto out_failed; + + u[i].type = QCOM_TEE_ARG_TYPE_IO; + } else { + u[i].type = QCOM_TEE_ARG_TYPE_OO; + } + } + } + + return 0; + +out_failed: + + /* Release whatever resources we got in 'u'. */ + marshal_in_req_cleanup(u, 0); + + /* Drop TEE istances; on Success TEE does that. */ + for (i = 0; u[i].type; i++) { + if (u[i].type == QCOM_TEE_ARG_TYPE_IO) + put_qcom_tee_object(u[i].o); + } + + return -1; +} + +static int marshal_out_req(struct qcom_tee_param params[], struct qcom_tee_arg u[]) +{ + int i = 0, err = 0; + + /* Consumes 'u' as initialized by marshal_in_req. */ + + for (i = 0; u[i].type; i++) { + switch (u[i].type) { + case QCOM_TEE_ARG_TYPE_OB: + params[i].buffer.len = u[i].b.size; + + break; + case QCOM_TEE_ARG_TYPE_IO: + put_qcom_tee_object(u[i].o); + + break; + case QCOM_TEE_ARG_TYPE_OO: + if (err) { + /* On FAILURE, continue to put objects. */ + params[i].attr = QCOM_TEE_OBJECT_NULL; + put_qcom_tee_object(u[i].o); + } else if (get_param_from_qcom_tee_object(u[i].o, ¶ms[i], NULL)) { + put_qcom_tee_object(u[i].o); + + err = -1; + } + + break; + case QCOM_TEE_ARG_TYPE_IB: + default: + break; + } + } + + if (!err) + return 0; + + /* Release whatever resources we got in 'params'. */ + for (i = 0; u[i].type; i++) { + if (params[i].attr == QCOM_TEE_OBJECT) + ; /* TODO. Cleanup exported object. */ + } + + return -1; +} + +static int marshal_in_cb_req(struct qcom_tee_param params[], u64 ubuf, + struct server_info *target_si, struct qcom_tee_arg u[]) +{ + int i, err = 0; + + size_t offset = 0; + + for (i = 0; u[i].type; i++) { + switch (u[i].type) { + case QCOM_TEE_ARG_TYPE_IB: + case QCOM_TEE_ARG_TYPE_OB: + params[i].attr = QCOM_TEE_BUFFER; + params[i].direction = u[i].type & QCOM_TEE_ARG_TYPE_INPUT_MASK; + params[i].buffer.addr = ubuf + offset; + params[i].buffer.len = u[i].b.size; + + offset = ALIGN(offset + u[i].b.size, 8); + + if (u[i].type == QCOM_TEE_ARG_TYPE_IB) { + void __user *uaddr = u64_to_user_ptr(params[i].buffer.addr); + + if (copy_to_user(uaddr, u[i].b.addr, u[i].b.size)) + return -1; + } + + break; + case QCOM_TEE_ARG_TYPE_IO: { + struct server_info *si; + + if (!err) { + params[i].direction = 1; + if (get_param_from_qcom_tee_object(u[i].o, ¶ms[i], &si)) { + put_qcom_tee_object(u[i].o); + + err = -1; + } else if (target_si && si && si != target_si) { + err = -1; + } + } else { + params[i].attr = QCOM_TEE_OBJECT_NULL; + + put_qcom_tee_object(u[i].o); + } + } + + break; + case QCOM_TEE_ARG_TYPE_OO: + params[i].attr = QCOM_TEE_OBJECT_NULL; + params[i].direction = 0; + + break; + default: + break; + } + } + + if (!err) + return 0; + + /* Release whatever resources we got in 'params'. */ + for (i = 0; u[i].type; i++) { + if (params[i].attr == QCOM_TEE_OBJECT) + ; /* TODO. Cleanup exported object. */ + } + + return -1; +} + +static int marshal_out_cb_req(struct qcom_tee_arg u[], struct qcom_tee_param params[]) +{ + int i; + + for (i = 0; u[i].type; i++) { + switch (u[i].type) { + case QCOM_TEE_ARG_TYPE_OB: { + void __user *uaddr = u64_to_user_ptr(params[i].buffer.addr); + + u[i].b.size = params[i].buffer.len; + if (copy_from_user(u[i].b.addr, uaddr, params[i].buffer.len)) + return -1; + } + + break; + case QCOM_TEE_ARG_TYPE_OO: + if (get_qcom_tee_object_from_param(¶ms[i], &u[i])) { + /* TODO. Release whatever resources we got in 'u'.*/ + return -1; + } + + break; + case QCOM_TEE_ARG_TYPE_IO: + case QCOM_TEE_ARG_TYPE_IB: + default: + break; + } + } + + return 0; +} + +/* Transaction management. */ +/* TODO. Do better! */ + +static struct cb_txn *txn_alloc(void) +{ + struct cb_txn *txn; + + txn = kzalloc(sizeof(*txn), GFP_KERNEL); + if (txn) { + kref_init(&txn->refcount); + + INIT_LIST_HEAD(&txn->node); + init_completion(&txn->completion); + } + + return txn; +} + +static void txn_free(struct cb_txn *txn) +{ + kfree(txn->uargs); + kfree(txn); +} + +/* queue_txn - queue a transaction only if server 'si' is alive. */ +static int queue_txn(struct server_info *si, struct cb_txn *txn) +{ + int dead; + + mutex_lock(&si_mutex); + dead = si->dead; + if (!dead) { + list_add(&txn->node, &si->cb_tx_list); + + txn->processing = XST_PENDING; + } + mutex_unlock(&si_mutex); + + return dead; +} + +static struct cb_txn *dequeue_txn_by_id(struct server_info *si, unsigned int id) +{ + struct cb_txn *txn; + + mutex_lock(&si_mutex); + list_for_each_entry(txn, &si->cb_tx_list, node) + if (txn->uargs->request_id == id) { + list_del_init(&txn->node); + + goto found; + } + + /* Invalid id. */ + txn = NULL; + +found: + mutex_unlock(&si_mutex); + + return txn; +} + +/** + * possible__txn_state_transition - Return possible state transition. + * @txn: Transactione to update. + * @state: Target state for @txn. + * + * Checks if the requested state transition for @txn is possible. + * Returns @state if the transition is possible or if @txn is already in @state state. + * Returns current @txn state if the transition is not possible. + */ +static enum state possible__txn_state_transition(struct cb_txn *txn, enum state state) +{ + /* Possible state transitions: + * PENDING -> PROCESSING, TIMEDOUT. + * PROCESSING -> PROCESSED, TIMEDOUT. + */ + + /* Moving to PROCESSING state; we should be in PENDING state. */ + if (state == XST_PROCESSING) { + if (txn->processing != XST_PENDING) + return txn->processing; + + /* Moving to PROCESSED state; we should be in PROCESSING state. */ + } else if (state == XST_PROCESSED) { + if (txn->processing != XST_PROCESSING) + return txn->processing; + + /* Moving to TIMEDOUT state; we should be in PENDING or PROCESSING state. */ + } else if (state == XST_TIMEDOUT) { + if (txn->processing != XST_PENDING && txn->processing != XST_PROCESSING) + return txn->processing; + + } else { + return txn->processing; + } + + return state; +} + +static int set_txn_state_locked(struct cb_txn *txn, enum state state) +{ + enum state pstate; + + pstate = possible__txn_state_transition(txn, state); + if (pstate == state) { + txn->processing = state; + + return 0; + } + + return -EINVAL; +} + +static struct cb_txn *get_txn_for_state_transition_locked(struct server_info *si, + unsigned int id, enum state state) +{ + struct cb_txn *txn; + + /* Supported state transitions: + * PENDING -> PROCESSING. + * PROCESSING -> PROCESSED. + */ + + if (state != XST_PROCESSING && state != XST_PROCESSED) + return NULL; + + list_for_each_entry(txn, &si->cb_tx_list, node) { + /* Search for a specific transaction with a particular state?! */ + if (id != CONTEXT_ID_ANY && txn->uargs->request_id != id) + continue; + + if (txn->processing != state && + possible__txn_state_transition(txn, state) == state) { + kref_get(&txn->refcount); + + return txn; + } + } + + return NULL; +} + +static struct cb_txn *get_txn_for_state_transition(struct server_info *si, + unsigned int context_id, enum state state) +{ + struct cb_txn *txn; + + mutex_lock(&si_mutex); + txn = get_txn_for_state_transition_locked(si, context_id, state); + mutex_unlock(&si_mutex); + + return txn; +} + +static int set_txn_state(struct cb_txn *txn, enum state state) +{ + int ret; + + mutex_lock(&si_mutex); + ret = set_txn_state_locked(txn, state); + mutex_unlock(&si_mutex); + + return ret; +} + +static void __release_txn(struct kref *refcount) +{ + struct cb_txn *txn = container_of(refcount, struct cb_txn, refcount); + + txn_free(txn); +} + +static void put_txn(struct cb_txn *txn) +{ + kref_put(&txn->refcount, __release_txn); +} + +static void dequeue_and_put_txn(struct cb_txn *txn) +{ + mutex_lock(&si_mutex); + /* Only if it is queued. */ + if (txn->processing != XST_NEW) + list_del_init(&txn->node); + mutex_unlock(&si_mutex); + + put_txn(txn); +} + +/* wait_for_pending_txn picks the next available pending transaction or sleep. */ +static int wait_for_pending_txn(struct server_info *si, struct cb_txn **picked_txn) +{ + int ret = 0; + struct cb_txn *txn; + + DEFINE_WAIT_FUNC(wait, woken_wake_function); + + add_wait_queue(&si->server_threads, &wait); + while (1) { + if (signal_pending(current)) { + ret = -ERESTARTSYS; + + break; + } + + mutex_lock(&si_mutex); + txn = get_txn_for_state_transition_locked(si, CONTEXT_ID_ANY, XST_PROCESSING); + if (txn) { + /* ''PENDING -> PROCESSING''. */ + set_txn_state_locked(txn, XST_PROCESSING); + mutex_unlock(&si_mutex); + + break; + } + mutex_unlock(&si_mutex); + + wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); + } + + remove_wait_queue(&si->server_threads, &wait); + *picked_txn = txn; + + return ret; +} + +/* Callback object's operations. */ + +static int cbo_dispatch(unsigned int context_id, + struct qcom_tee_object *object, unsigned long op, struct qcom_tee_arg *args) +{ + struct cb_txn *txn; + struct cb_object *cb_object = to_cb_object(object); + + int errno, num_params = size_of_arg(args); + + txn = txn_alloc(); + if (!txn) + return -ENOMEM; + + /* INIT and QUEUE the request. */ + + txn->args = args; + txn->uargs_size = offsetof(struct qcom_tee_cb_arg, params) + + (num_params * sizeof(txn->uargs->params[0])); + + txn->uargs = kzalloc(txn->uargs_size, GFP_KERNEL); + if (!txn->args) { + put_txn(txn); + + return -EINVAL; + } + + txn->uargs->id = cb_object->id; + txn->uargs->op = op; + txn->uargs->request_id = context_id; + txn->uargs->num_params = num_params; + + if (queue_txn(cb_object->si, txn)) { + put_txn(txn); + + return -EINVAL; + } + + wake_up_interruptible_all(&cb_object->si->server_threads); + + if (context_id == CONTEXT_ID_ANY) + return 0; + + wait_for_completion_state(&txn->completion, TASK_FREEZABLE); + + /* TODO. Allow TASK_KILLABLE. */ + /* We do not care why wait_for_completion_state returend. + * The fastest way to exit the dispatcher is to TIMEOUT the transaction. + * However, if set_txn_state failed, then transaction has already been PROCESSED. + */ + + errno = set_txn_state(txn, XST_TIMEDOUT) ? txn->errno : -EINVAL; + if (errno) + dequeue_and_put_txn(txn); + + return errno; +} + +static void cbo_notify(unsigned int context_id, struct qcom_tee_object *object, int status) +{ + struct cb_txn *txn; + + txn = dequeue_txn_by_id(to_cb_object(object)->si, context_id); + if (txn) { + int i; + struct qcom_tee_arg *u = txn->args; + + for (i = 0; u[i].type; i++) { + if (u[i].type == QCOM_TEE_ARG_TYPE_OO) { + /* Transport failed. TEE did not recived the objects. */ + if (status && (typeof_qcom_tee_object(u[i].o) != + QCOM_TEE_OBJECT_TYPE_USER)) + put_qcom_tee_object(u[i].o); + + put_qcom_tee_object(u[i].o); + } + } + + put_txn(txn); + } +} + +static void ____destroy_server_info(struct kref *kref); +static void cbo_release(struct qcom_tee_object *object) +{ + struct cb_object *cb_object = to_cb_object(object); + + if (cb_object->notify_on_release) { + static struct qcom_tee_arg args[] = { { .type = QCOM_TEE_ARG_TYPE_END } }; + + /* Use 'CONTEXT_ID_ANY' as context ID; as we do not care about the results. */ + cbo_dispatch(CONTEXT_ID_ANY, object, QCOM_TEE_OBJECT_OP_RELEASE, args); + } + + /* The matching 'kref_get' is in 'cb_object_alloc'. */ + kref_put(&cb_object->si->refcount, ____destroy_server_info); + kfree(cb_object); +} + +static struct qcom_tee_object_operations cbo_ops = { + .release = cbo_release, + .notify = cbo_notify, + .dispatch = cbo_dispatch, +}; + +/* User Callback server */ + +static int server_open(struct inode *nodp, struct file *filp) +{ + struct server_info *si; + + si = kzalloc(sizeof(*si), GFP_KERNEL); + if (!si) + return -ENOMEM; + + kref_init(&si->refcount); + INIT_LIST_HEAD(&si->cb_tx_list); + init_waitqueue_head(&si->server_threads); + + filp->private_data = ROOT_QCOM_TEE_OBJECT; + + return 0; +} + +static long qtee_ioctl_receive(struct server_info *si, u64 uargs, size_t len) +{ + struct cb_txn *txn; + u64 ubuf; + + do { + /* WAIT FOR A REQUEST ... */ + if (wait_for_pending_txn(si, &txn)) + return -ERESTARTSYS; + + /* Extra user buffer used for buffer arguments. */ + ubuf = ALIGN(uargs + txn->uargs_size, 8); + + /* Initialize param. */ + /* The remaining fields are already initialized in cbo_dispatch. */ + if (marshal_in_cb_req(txn->uargs->params, ubuf, si, txn->args)) + goto out_failed; + + if (copy_to_user((void __user *)uargs, txn->uargs, txn->uargs_size)) { + /* TODO. We need to do some cleanup for marshal_in_cb_req. */ + goto out_failed; + } + + break; + +out_failed: + /* FAILED parsing a request. Notify TEE and try another one. */ + + if (txn->uargs->request_id == CONTEXT_ID_ANY) + dequeue_and_put_txn(txn); + else + complete(&txn->completion); + + put_txn(txn); + } while (1); + + return 0; +} + +static long qtee_ioctl_reply(struct server_info *si, u64 uargs, size_t len) +{ + struct qcom_tee_cb_arg args; + struct cb_txn *txn; + + int errno; + + if (copy_from_user(&args, (void __user *)uargs, sizeof(args))) + return -EFAULT; + + /* 'CONTEXT_ID_ANY' context ID?! Ignore. */ + if (args.request_id == CONTEXT_ID_ANY) + return 0; + + txn = get_txn_for_state_transition(si, args.request_id, XST_PROCESSED); + if (!txn) + return -EINVAL; + + errno = args.result; + if (!errno) { + /* Only parse arguments on SUCCESS. */ + + /* TODO. Do not copy the header again, but let's keep it simple for now. */ + if (copy_from_user(txn->uargs, (void __user *)uargs, txn->uargs_size)) { + errno = -EFAULT; + } else { + if (marshal_out_cb_req(txn->args, txn->uargs->params)) + errno = -EINVAL; + } + } + + txn->errno = errno; + + if (set_txn_state(txn, XST_PROCESSED)) + ; /* TODO. We need to do some cleanup for marshal_out_cb_req on !errno. */ + else + complete(&txn->completion); + + put_txn(txn); + + return errno; +} + +static long server_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct qcom_tee_ioctl_data data; + + if (_IOC_SIZE(cmd) != sizeof(data)) + return -EINVAL; + + if (copy_from_user(&data, (void __user *)arg, sizeof(data))) + return -EFAULT; + + switch (cmd) { + case QCOM_TEE_IOCTL_RECEIVE: + return qtee_ioctl_receive(filp->private_data, data.buf_ptr, data.buf_len); + + case QCOM_TEE_IOCTL_REPLY: + return qtee_ioctl_reply(filp->private_data, data.buf_ptr, data.buf_len); + + default: + return -ENOIOCTLCMD; + } +} + +static void ____destroy_server_info(struct kref *kref) +{ + struct server_info *si = container_of(kref, struct server_info, refcount); + + kfree(si); +} + +static int server_release(struct inode *nodp, struct file *filp) +{ + struct server_info *si = filp->private_data; + + mutex_lock(&si_mutex); + si->dead = 1; + + /* TODO. Teminate any PENDING or PROCESSING transactions. */ + + mutex_unlock(&si_mutex); + kref_put(&si->refcount, ____destroy_server_info); + + return 0; +} + +static const struct file_operations server_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = server_ioctl, + .compat_ioctl = server_ioctl, + .release = server_release, + .open = server_open, +}; + +/* TEE object invocation. */ + +static long qtee_ioctl_invoke(struct qcom_tee_object *object, + struct qcom_tee_object_invoke_arg __user *uargs, size_t len) +{ + int ret; + + struct qcom_tee_object_invoke_arg args; + struct qcom_tee_object_invoke_ctx *oic; + struct qcom_tee_param *params; + struct qcom_tee_arg *u; + + if (copy_from_user(&args, (void __user *)uargs, sizeof(args))) + return -EFAULT; + + oic = kzalloc(sizeof(*oic), GFP_KERNEL); + if (!oic) + return -ENOMEM; + + params = kcalloc(args.num_params, sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out_failed; + } + + /* Plus one for 'QCOM_TEE_ARG_TYPE_END'. */ + u = kcalloc(args.num_params + 1, sizeof(*u), GFP_KERNEL); + if (!u) { + ret = -ENOMEM; + goto out_failed; + } + + /* Copy argument array from userspace. */ + if (copy_from_user(params, (void __user *)uargs->params, + sizeof(*params) * args.num_params)) { + ret = -EFAULT; + goto out_failed; + } + + /* INITIATE an invocation. */ + + if (marshal_in_req(u, params, args.num_params)) { + pr_err("marshal_in_req failed.\n"); + ret = -EINVAL; + goto out_failed; + } + + ret = qcom_tee_object_do_invoke(oic, object, args.op, u, &args.result); + if (ret) { + /* TODO. We need to do some cleanup for marshal_in_req. */ + goto out_failed; + } + + if (!args.result) { + if (marshal_out_req(params, u)) { + pr_err("marshal_out_req failed.\n"); + ret = -EINVAL; + goto out_failed; + } + + if (copy_to_user((void __user *)uargs->params, params, + sizeof(*params) * args.num_params)) { + ret = -EFAULT; + + /* TODO. We need to do some cleanup for marshal_out_req. */ + + goto out_failed; + } + } + + /* Copy u_req.result back! */ + if (copy_to_user(uargs, &args, sizeof(args))) { + ret = -EFAULT; + + goto out_failed; + } + + ret = 0; + +out_failed: + kfree(u); + kfree(params); + kfree(oic); + + return ret; +} + +static long qtee_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct qcom_tee_ioctl_data data; + + if (_IOC_SIZE(cmd) != sizeof(data)) + return -EINVAL; + + if (copy_from_user(&data, (void __user *)arg, sizeof(data))) + return -EFAULT; + + switch (cmd) { + case QCOM_TEE_IOCTL_INVOKE: + return qtee_ioctl_invoke(filp->private_data, + (struct qcom_tee_object_invoke_arg __user *)data.buf_ptr, data.buf_len); + + default: + return -ENOIOCTLCMD; + } +} + +static int qtee_release(struct inode *nodp, struct file *filp) +{ + struct qcom_tee_object *object = filp->private_data; + + /* The matching get_qcom_tee_object is in get_param_from_qcom_tee_object. */ + put_qcom_tee_object(object); + + return 0; +} + +static const struct file_operations qtee_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qtee_ioctl, + .compat_ioctl = qtee_ioctl, + .release = qtee_release, +}; + +/* ''ROOT Object'' */ + +static int root_open(struct inode *nodp, struct file *filp) +{ + /* Always return the same instance of root qcom_tee_object. */ + filp->private_data = ROOT_QCOM_TEE_OBJECT; + + return 0; +} + +static const struct file_operations root_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qtee_ioctl, + .compat_ioctl = qtee_ioctl, + .open = root_open, +}; + +/* Device for direct object invocation. */ +static struct miscdevice smcinvoke_misc_qtee_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qtee", + .fops = &root_fops, +}; + +/* Device to start a userspace object host, i.e. a callback server. */ +static struct miscdevice smcinvoke_misc_qtee_ree_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qtee-ree", + .fops = &server_fops, +}; + +static int smcinvoke_probe(struct platform_device *pdev) +{ + int ret; + + ret = misc_register(&smcinvoke_misc_qtee_device); + if (ret) + return ret; + + ret = misc_register(&smcinvoke_misc_qtee_ree_device); + if (ret) { + misc_deregister(&smcinvoke_misc_qtee_device); + + return ret; + } + + return 0; +} + +static const struct of_device_id smcinvoke_match[] = { + { .compatible = "qcom,smcinvoke", }, {}, +}; + +static struct platform_driver smcinvoke_plat_driver = { + .probe = smcinvoke_probe, + .driver = { + .name = "smcinvoke", + .of_match_table = smcinvoke_match, + }, +}; + +module_platform_driver(smcinvoke_plat_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("smcinvoke driver"); +MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); +MODULE_IMPORT_NS(DMA_BUF); diff --git a/include/uapi/misc/qcom_tee.h b/include/uapi/misc/qcom_tee.h new file mode 100644 index 000000000000..7c127efc9612 --- /dev/null +++ b/include/uapi/misc/qcom_tee.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef __QCOM_TEE_H__ +#define __QCOM_TEE_H__ + +#include <linux/types.h> + +/** + * struct qcom_tee_ioctl_data - Buffer to pass arguments to IOCTL call. + * @buf_ptr: a __user pointer to a buffer. + * @buf_len: length of the buffer. + * + * Used for QCOM_TEE_IOCTL_INVOKE, QCOM_TEE_IOCTL_RECEIVE, and QCOM_TEE_IOCTL_REPLY. + */ +struct qcom_tee_ioctl_data { + __u64 buf_ptr; + __u64 buf_len; +}; + +#define QCOM_TEE_IOCTL_INVOKE _IOWR('Q', 0, struct qcom_tee_ioctl_data) +#define QCOM_TEE_IOCTL_RECEIVE _IOWR('Q', 1, struct qcom_tee_ioctl_data) +#define QCOM_TEE_IOCTL_REPLY _IOWR('Q', 2, struct qcom_tee_ioctl_data) + +enum qcom_tee_param_attr { + /* Buffer. */ + QCOM_TEE_BUFFER = 0, + /* A NULL object. */ + QCOM_TEE_OBJECT_NULL = 0x80, + /* An object. */ + QCOM_TEE_OBJECT = QCOM_TEE_OBJECT_NULL + 1, +}; + +/** + * Objects can be hosted on secure side, or privileged nonsecure side. + * host_id in struct qcom_tee_param specifies the object host. + * + * For remote objects, use QCOM_TEE_OBJECT_SECURE. For objects, hosted in + * userspace, host_id is the file descriptor of the userspace server that host + * the object. Any negative number, is an object hosted in kernel. + */ + +#define QCOM_TEE_OBJECT_SECURE -1 +#define QCOM_TEE_MEMORY_OBJECT -2 + +/* Some helpers to check object host. */ + +#define QCOM_TEE_PARAM_OBJECT_SECURE(p) ((p)->object.host_id == QCOM_TEE_OBJECT_SECURE) +#define QCOM_TEE_PARAM_OBJECT_KERNEL(p) ((p)->object.host_id < QCOM_TEE_OBJECT_SECURE) +#define QCOM_TEE_PARAM_OBJECT_USER(p) ((p)->object.host_id > QCOM_TEE_OBJECT_SECURE) + +/** + * struct qcom_tee_param - Parameter to IOCTL calls. + * @attr: attributes from enum qcom_tee_param_attr. + * @direction: either input or output parameter. + * @object: an ID that represent the object. + * @buffer: a buffer. + * + * @id is the file descriptor that represents the object if @host_id is + * QCOM_TEE_OBJECT_KERNEL or QCOM_TEE_OBJECT_SECURE. Otherwise, it is a number + * that represents the object in the userspace process. + * + * @addr and @len represents a buffer which is copied to a shared buffer with + * secure side, i.e. it is not zero-copy. + * + * QCOM_TEE_OBJECT_NULL is valid everywhere, so @id and @host_id are ignored. + */ +struct qcom_tee_param { + __u32 attr; + __u32 direction; + + union { + struct { + __u64 id; + __s32 host_id; + } object; + + struct { + __u64 addr; + __u64 len; + } buffer; + }; +}; + +/** + * struct qcom_tee_object_invoke_arg - Invokes an object in QTEE. + * @op: operation specific to object. + * @result: return value. + * @num_params: number of parameters following this struct. + */ +struct qcom_tee_object_invoke_arg { + __u32 op; + __s32 result; + __u32 num_params; + struct qcom_tee_param params[]; +}; + +/** + * struct qcom_tee_cb_arg - Receive/Send object invocation from/to QTEE. + * @id: object ID being invoked. + * @request_id: ID of current request. + * @op: operation specific to object. + * @result: return value. + * @num_params: number of parameters following this struct. + * + * @params is initialized to represents number of input and output parameters + * and where the kernel expects to read the results. + */ +struct qcom_tee_cb_arg { + __u64 id; + __u32 request_id; + __u32 op; + __s32 result; + __u32 num_params; + struct qcom_tee_param params[]; +}; + +#endif /* __QCOM_TEE_H__ */ -- 2.34.1