diff --git a/drivers/tee/optee/supp.c b/drivers/tee/optee/supp.c index a3d11b1f90fa..06747e90c230 100644 --- a/drivers/tee/optee/supp.c +++ b/drivers/tee/optee/supp.c @@ -10,7 +10,11 @@ struct optee_supp_req { struct list_head link; + int id; + bool in_queue; + bool processed; + u32 func; u32 ret; size_t num_params; @@ -19,6 +23,9 @@ struct optee_supp_req { struct completion c; }; +/* It is temporary request used for revoked pending request in supp->idr. */ +#define INVALID_REQ_PTR ((struct optee_supp_req *)ERR_PTR(-EBADF)) + void optee_supp_init(struct optee_supp *supp) { memset(supp, 0, sizeof(*supp)); @@ -39,21 +46,23 @@ void optee_supp_release(struct optee_supp *supp) { int id; struct optee_supp_req *req; - struct optee_supp_req *req_tmp; mutex_lock(&supp->mutex); - /* Abort all request retrieved by supplicant */ + /* Abort all request */ idr_for_each_entry(&supp->idr, req, id) { idr_remove(&supp->idr, id); - req->ret = TEEC_ERROR_COMMUNICATION; - complete(&req->c); - } + /* Skip if request was already marked invalid */ + if (IS_ERR(req)) + continue; - /* Abort all queued requests */ - list_for_each_entry_safe(req, req_tmp, &supp->reqs, link) { - list_del(&req->link); - req->in_queue = false; + /* For queued requests where supplicant has not seen it */ + if (req->in_queue) { + list_del(&req->link); + req->in_queue = false; + } + + req->processed = true; req->ret = TEEC_ERROR_COMMUNICATION; complete(&req->c); } @@ -100,8 +109,16 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params, /* Insert the request in the request list */ mutex_lock(&supp->mutex); + req->id = idr_alloc(&supp->idr, req, 1, 0, GFP_KERNEL); + if (req->id < 0) { + mutex_unlock(&supp->mutex); + kfree(req); + return TEEC_ERROR_OUT_OF_MEMORY; + } + list_add_tail(&req->link, &supp->reqs); req->in_queue = true; + req->processed = false; mutex_unlock(&supp->mutex); /* Tell an eventual waiter there's a new request */ @@ -117,21 +134,43 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params, if (wait_for_completion_killable(&req->c)) { mutex_lock(&supp->mutex); if (req->in_queue) { + /* Supplicant has not seen this request yet. */ + idr_remove(&supp->idr, req->id); list_del(&req->link); req->in_queue = false; + + ret = TEEC_ERROR_COMMUNICATION; + } else if (req->processed) { + /* + * Supplicant has processed this request. Ignore the + * kill signal for now and submit the result. req is not + * in supp->reqs (removed by supp_pop_entry()) nor in + * supp->idr (removed by supp_pop_req()). + */ + ret = req->ret; + } else { + /* + * Supplicant is in the middle of processing this + * request. Replace req with INVALID_REQ_PTR so that + * the ID remains busy, causing optee_supp_send() to + * fail on the next call to supp_pop_req() with this ID. + */ + idr_replace(&supp->idr, INVALID_REQ_PTR, req->id); + ret = TEEC_ERROR_COMMUNICATION; } + mutex_unlock(&supp->mutex); - req->ret = TEEC_ERROR_COMMUNICATION; + } else { + ret = req->ret; } - ret = req->ret; kfree(req); return ret; } static struct optee_supp_req *supp_pop_entry(struct optee_supp *supp, - int num_params, int *id) + int num_params) { struct optee_supp_req *req; @@ -153,10 +192,6 @@ static struct optee_supp_req *supp_pop_entry(struct optee_supp *supp, return ERR_PTR(-EINVAL); } - *id = idr_alloc(&supp->idr, req, 1, 0, GFP_KERNEL); - if (*id < 0) - return ERR_PTR(-ENOMEM); - list_del(&req->link); req->in_queue = false; @@ -214,7 +249,6 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params, struct optee *optee = tee_get_drvdata(teedev); struct optee_supp *supp = &optee->supp; struct optee_supp_req *req = NULL; - int id; size_t num_meta; int rc; @@ -224,15 +258,11 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params, while (true) { mutex_lock(&supp->mutex); - req = supp_pop_entry(supp, *num_params - num_meta, &id); + req = supp_pop_entry(supp, *num_params - num_meta); + if (req) + break; /* Keep mutex held. */ mutex_unlock(&supp->mutex); - if (req) { - if (IS_ERR(req)) - return PTR_ERR(req); - break; - } - /* * If we didn't get a request we'll block in * wait_for_completion() to avoid needless spinning. @@ -245,6 +275,13 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params, return -ERESTARTSYS; } + /* supp->mutex held and req != NULL. */ + + if (IS_ERR(req)) { + mutex_unlock(&supp->mutex); + return PTR_ERR(req); + } + if (num_meta) { /* * tee-supplicant support meta parameters -> requsts can be @@ -252,13 +289,11 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params, */ param->attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT | TEE_IOCTL_PARAM_ATTR_META; - param->u.value.a = id; + param->u.value.a = req->id; param->u.value.b = 0; param->u.value.c = 0; } else { - mutex_lock(&supp->mutex); - supp->req_id = id; - mutex_unlock(&supp->mutex); + supp->req_id = req->id; } *func = req->func; @@ -266,6 +301,7 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params, memcpy(param + num_meta, req->param, sizeof(struct tee_param) * req->num_params); + mutex_unlock(&supp->mutex); return 0; } @@ -297,12 +333,17 @@ static struct optee_supp_req *supp_pop_req(struct optee_supp *supp, if (!req) return ERR_PTR(-ENOENT); + /* optee_supp_thrd_req() already returned to optee. */ + if (IS_ERR(req)) + goto failed_req; + if ((num_params - nm) != req->num_params) return ERR_PTR(-EINVAL); + *num_meta = nm; +failed_req: idr_remove(&supp->idr, id); supp->req_id = -1; - *num_meta = nm; return req; } @@ -328,10 +369,9 @@ int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, mutex_lock(&supp->mutex); req = supp_pop_req(supp, num_params, param, &num_meta); - mutex_unlock(&supp->mutex); - if (IS_ERR(req)) { - /* Something is wrong, let supplicant restart. */ + mutex_unlock(&supp->mutex); + /* Something is wrong, let supplicant handel it. */ return PTR_ERR(req); } @@ -355,9 +395,10 @@ int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, } } req->ret = ret; - + req->processed = true; /* Let the requesting thread continue */ complete(&req->c); + mutex_unlock(&supp->mutex); return 0; }