@@ -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
@@ -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
new file mode 100644
@@ -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);
new file mode 100644
@@ -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__ */
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@quicinc.com> --- 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(+)