#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "qcomtee.h"
struct qcomtee_user_object {
struct qcomtee_object object;
struct tee_context *ctx;
u64 object_id;
bool notify;
};
#define to_qcomtee_user_object(o) \
container_of((o), struct qcomtee_user_object, object)
static struct qcomtee_object_operations qcomtee_user_object_ops;
int is_qcomtee_user_object(struct qcomtee_object *object)
{
return object != NULL_QCOMTEE_OBJECT &&
typeof_qcomtee_object(object) == QCOMTEE_OBJECT_TYPE_CB &&
object->ops == &qcomtee_user_object_ops;
}
void qcomtee_user_object_set_notify(struct qcomtee_object *object, bool notify)
{
if (is_qcomtee_user_object(object))
to_qcomtee_user_object(object)->notify = notify;
}
enum qcomtee_req_state {
QCOMTEE_REQ_QUEUED = 1,
QCOMTEE_REQ_PROCESSING,
QCOMTEE_REQ_PROCESSED,
};
struct qcomtee_ureq {
enum qcomtee_req_state state;
int req_id;
u64 object_id;
u32 op;
struct qcomtee_arg *args;
int errno;
struct list_head node;
struct completion c;
};
static struct qcomtee_ureq empty_ureq = { .state = QCOMTEE_REQ_PROCESSING };
static int ureq_enqueue(struct qcomtee_context_data *ctxdata,
struct qcomtee_ureq *ureq)
{
int ret;
guard(mutex)(&ctxdata->reqs_lock);
if (ctxdata->released)
return -ENODEV;
ret = idr_alloc(&ctxdata->reqs_idr, ureq, 0, 0, GFP_KERNEL);
if (ret < 0)
return ret;
ureq->req_id = ret;
ureq->state = QCOMTEE_REQ_QUEUED;
list_add_tail(&ureq->node, &ctxdata->reqs_list);
return 0;
}
static struct qcomtee_ureq *ureq_dequeue(struct qcomtee_context_data *ctxdata,
int req_id)
{
struct qcomtee_ureq *ureq;
ureq = idr_remove(&ctxdata->reqs_idr, req_id);
if (ureq == &empty_ureq || !ureq)
return NULL;
list_del(&ureq->node);
return ureq;
}
static struct qcomtee_ureq *ureq_select(struct qcomtee_context_data *ctxdata,
size_t ubuf_size, int num_params)
{
struct qcomtee_ureq *req, *ureq = NULL;
struct qcomtee_arg *u;
int i;
list_for_each_entry(req, &ctxdata->reqs_list, node) {
if (req->state == QCOMTEE_REQ_QUEUED) {
ureq = req;
break;
}
}
if (!ureq)
return NULL;
u = ureq->args;
if (num_params < qcomtee_args_len(u))
return ERR_PTR(-EINVAL);
qcomtee_arg_for_each_input_buffer(i, u) {
ubuf_size = size_sub(ubuf_size, u[i].b.size);
if (ubuf_size == SIZE_MAX)
return ERR_PTR(-EINVAL);
ubuf_size = round_down(ubuf_size, 8);
}
return ureq;
}
void qcomtee_requests_destroy(struct qcomtee_context_data *ctxdata)
{
struct qcomtee_ureq *req, *ureq;
guard(mutex)(&ctxdata->reqs_lock);
ctxdata->released = true;
list_for_each_entry_safe(ureq, req, &ctxdata->reqs_list, node) {
ureq_dequeue(ctxdata, ureq->req_id);
if (ureq->op != QCOMTEE_MSG_OBJECT_OP_RELEASE) {
ureq->state = QCOMTEE_REQ_PROCESSED;
ureq->errno = -ENODEV;
complete(&ureq->c);
} else {
kfree(ureq);
}
}
}
static int qcomtee_user_object_dispatch(struct qcomtee_object_invoke_ctx *oic,
struct qcomtee_object *object, u32 op,
struct qcomtee_arg *args)
{
struct qcomtee_user_object *uo = to_qcomtee_user_object(object);
struct qcomtee_context_data *ctxdata = uo->ctx->data;
int errno;
struct qcomtee_ureq *ureq __free(kfree) = kzalloc(sizeof(*ureq),
GFP_KERNEL);
if (!ureq)
return -ENOMEM;
init_completion(&ureq->c);
ureq->object_id = uo->object_id;
ureq->op = op;
ureq->args = args;
if (ureq_enqueue(ctxdata, ureq))
return -ENODEV;
complete(&ctxdata->req_c);
if (!wait_for_completion_state(&ureq->c,
TASK_KILLABLE | TASK_FREEZABLE)) {
errno = ureq->errno;
if (!errno)
oic->data = no_free_ptr(ureq);
} else {
enum qcomtee_req_state prev_state;
errno = -ENODEV;
scoped_guard(mutex, &ctxdata->reqs_lock) {
prev_state = ureq->state;
if (prev_state == QCOMTEE_REQ_PROCESSING) {
list_del(&ureq->node);
idr_replace(&ctxdata->reqs_idr,
&empty_ureq, ureq->req_id);
} else if (prev_state == QCOMTEE_REQ_QUEUED) {
ureq_dequeue(ctxdata, ureq->req_id);
}
}
if (prev_state == QCOMTEE_REQ_PROCESSED) {
errno = ureq->errno;
if (!errno)
oic->data = no_free_ptr(ureq);
}
}
return errno;
}
static void qcomtee_user_object_notify(struct qcomtee_object_invoke_ctx *oic,
struct qcomtee_object *unused_object,
int err)
{
struct qcomtee_ureq *ureq = oic->data;
struct qcomtee_arg *u = ureq->args;
int i;
qcomtee_arg_for_each_output_object(i, u) {
if (err &&
(typeof_qcomtee_object(u[i].o) == QCOMTEE_OBJECT_TYPE_CB))
qcomtee_object_put(u[i].o);
qcomtee_object_put(u[i].o);
}
kfree(ureq);
}
static void qcomtee_user_object_release(struct qcomtee_object *object)
{
struct qcomtee_user_object *uo = to_qcomtee_user_object(object);
struct qcomtee_context_data *ctxdata = uo->ctx->data;
struct qcomtee_ureq *ureq;
static struct qcomtee_arg args[] = { { .type = QCOMTEE_ARG_TYPE_INV } };
if (!uo->notify)
goto out_no_notify;
ureq = kzalloc_obj(*ureq);
if (!ureq)
goto out_no_notify;
ureq->object_id = uo->object_id;
ureq->op = QCOMTEE_MSG_OBJECT_OP_RELEASE;
ureq->args = args;
if (ureq_enqueue(ctxdata, ureq)) {
kfree(ureq);
goto out_no_notify;
}
complete(&ctxdata->req_c);
out_no_notify:
teedev_ctx_put(uo->ctx);
kfree(uo);
}
static struct qcomtee_object_operations qcomtee_user_object_ops = {
.release = qcomtee_user_object_release,
.notify = qcomtee_user_object_notify,
.dispatch = qcomtee_user_object_dispatch,
};
int qcomtee_user_param_to_object(struct qcomtee_object **object,
struct tee_param *param,
struct tee_context *ctx)
{
int err;
struct qcomtee_user_object *user_object __free(kfree) =
kzalloc_obj(*user_object);
if (!user_object)
return -ENOMEM;
user_object->ctx = ctx;
user_object->object_id = param->u.objref.id;
user_object->notify = true;
err = qcomtee_object_user_init(&user_object->object,
QCOMTEE_OBJECT_TYPE_CB,
&qcomtee_user_object_ops, "uo-%llu",
param->u.objref.id);
if (err)
return err;
teedev_ctx_get(ctx);
*object = &no_free_ptr(user_object)->object;
return 0;
}
int qcomtee_user_param_from_object(struct tee_param *param,
struct qcomtee_object *object,
struct tee_context *ctx)
{
struct qcomtee_user_object *uo;
uo = to_qcomtee_user_object(object);
if (uo->ctx != ctx)
return -EINVAL;
param->u.objref.id = uo->object_id;
param->u.objref.flags = QCOMTEE_OBJREF_FLAG_USER;
qcomtee_object_put(object);
return 0;
}
static int qcomtee_cb_params_from_args(struct tee_param *params,
struct qcomtee_arg *u, int num_params,
void __user *ubuf_addr, size_t ubuf_size,
struct tee_context *ctx)
{
int i, np;
void __user *uaddr;
qcomtee_arg_for_each(i, u) {
switch (u[i].type) {
case QCOMTEE_ARG_TYPE_IB:
params[i].attr = TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INPUT;
ubuf_size = round_down(ubuf_size - u[i].b.size, 8);
uaddr = (void __user *)(ubuf_addr + ubuf_size);
params[i].u.ubuf.uaddr = uaddr;
params[i].u.ubuf.size = u[i].b.size;
if (copy_to_user(params[i].u.ubuf.uaddr, u[i].b.addr,
u[i].b.size))
goto out_failed;
break;
case QCOMTEE_ARG_TYPE_OB:
params[i].attr = TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT;
params[i].u.ubuf.size = u[i].b.size;
break;
case QCOMTEE_ARG_TYPE_IO:
params[i].attr = TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT;
if (qcomtee_objref_from_arg(¶ms[i], &u[i], ctx))
goto out_failed;
break;
case QCOMTEE_ARG_TYPE_OO:
params[i].attr =
TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT;
break;
default:
goto out_failed;
}
}
return i;
out_failed:
for (np = i; np >= 0; np--) {
if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT)
qcomtee_context_del_qtee_object(¶ms[np], ctx);
}
for (; u[i].type; i++) {
if (u[i].type == QCOMTEE_ARG_TYPE_IO)
qcomtee_object_put(u[i].o);
}
return -EINVAL;
}
static int qcomtee_cb_params_to_args(struct qcomtee_arg *u,
struct tee_param *params, int num_params,
struct tee_context *ctx)
{
int i;
qcomtee_arg_for_each(i, u) {
switch (u[i].type) {
case QCOMTEE_ARG_TYPE_IB:
if (params[i].attr !=
TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INPUT)
goto out_failed;
break;
case QCOMTEE_ARG_TYPE_OB:
if (params[i].attr !=
TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT)
goto out_failed;
if (params[i].u.ubuf.size > u[i].b.size)
goto out_failed;
if (copy_from_user(u[i].b.addr, params[i].u.ubuf.uaddr,
params[i].u.ubuf.size))
goto out_failed;
u[i].b.size = params[i].u.ubuf.size;
break;
case QCOMTEE_ARG_TYPE_IO:
if (params[i].attr !=
TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT)
goto out_failed;
break;
case QCOMTEE_ARG_TYPE_OO:
if (params[i].attr !=
TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT)
goto out_failed;
if (qcomtee_objref_to_arg(&u[i], ¶ms[i], ctx))
goto out_failed;
break;
default:
goto out_failed;
}
}
return 0;
out_failed:
for (i--; i >= 0; i--) {
if (u[i].type != QCOMTEE_ARG_TYPE_OO)
continue;
qcomtee_user_object_set_notify(u[i].o, false);
if (typeof_qcomtee_object(u[i].o) == QCOMTEE_OBJECT_TYPE_CB)
qcomtee_object_put(u[i].o);
qcomtee_object_put(u[i].o);
}
return -EINVAL;
}
int qcomtee_user_object_select(struct tee_context *ctx,
struct tee_param *params, int num_params,
void __user *uaddr, size_t size,
struct qcomtee_user_object_request_data *data)
{
struct qcomtee_context_data *ctxdata = ctx->data;
struct qcomtee_ureq *ureq;
int ret;
while (1) {
scoped_guard(mutex, &ctxdata->reqs_lock) {
ureq = ureq_select(ctxdata, size, num_params);
if (!ureq)
goto wait_for_request;
if (IS_ERR(ureq))
return PTR_ERR(ureq);
ureq->state = QCOMTEE_REQ_PROCESSING;
data->id = ureq->req_id;
data->object_id = ureq->object_id;
data->op = ureq->op;
ret = qcomtee_cb_params_from_args(params, ureq->args,
num_params, uaddr,
size, ctx);
if (ret >= 0)
goto done_request;
ureq_dequeue(ctxdata, data->id);
ureq->state = QCOMTEE_REQ_PROCESSED;
ureq->errno = ret;
complete(&ureq->c);
}
continue;
wait_for_request:
if (wait_for_completion_interruptible(&ctxdata->req_c))
return -ERESTARTSYS;
}
done_request:
if (data->op == QCOMTEE_MSG_OBJECT_OP_RELEASE) {
scoped_guard(mutex, &ctxdata->reqs_lock)
ureq_dequeue(ctxdata, data->id);
kfree(ureq);
}
data->np = ret;
return 0;
}
int qcomtee_user_object_submit(struct tee_context *ctx,
struct tee_param *params, int num_params,
int req_id, int errno)
{
struct qcomtee_context_data *ctxdata = ctx->data;
struct qcomtee_ureq *ureq;
guard(mutex)(&ctxdata->reqs_lock);
ureq = ureq_dequeue(ctxdata, req_id);
if (!ureq)
return -EINVAL;
ureq->state = QCOMTEE_REQ_PROCESSED;
if (!errno)
ureq->errno = qcomtee_cb_params_to_args(ureq->args, params,
num_params, ctx);
else
ureq->errno = errno;
if (!errno && ureq->errno)
errno = ureq->errno;
else
errno = 0;
complete(&ureq->c);
return errno;
}