fastrpc_rpmsg_callback() drops the invoke reference taken before sending the request. The callback can currently consume a reference for a guessed context ID before the request has been sent, and duplicate replies can schedule multiple puts for the same send reference.
Track whether an invoke has been sent and whether a reply has already been accepted while holding the channel IDR lock. Reject unsent and duplicate replies so only the first valid response consumes the send reference. Signed-off-by: Yousef Alhouseen <[email protected]> --- drivers/misc/fastrpc.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index c91d5da42..4eaecee1b 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -250,6 +250,8 @@ struct fastrpc_invoke_ctx { uintptr_t *buf_pv; struct fastrpc_buf_overlap *olaps; struct fastrpc_channel_ctx *cctx; + bool sent; + bool completed; }; struct fastrpc_session_ctx { @@ -1239,6 +1241,8 @@ static int fastrpc_invoke_send(struct fastrpc_session_ctx *sctx, struct fastrpc_channel_ctx *cctx; struct fastrpc_user *fl = ctx->fl; struct fastrpc_msg *msg = &ctx->msg; + unsigned long flags; + bool put_ref = true; int ret; cctx = fl->cctx; @@ -1255,10 +1259,23 @@ static int fastrpc_invoke_send(struct fastrpc_session_ctx *sctx, msg->size = roundup(ctx->msg_sz, PAGE_SIZE); fastrpc_context_get(ctx); + spin_lock_irqsave(&cctx->lock, flags); + ctx->sent = true; + spin_unlock_irqrestore(&cctx->lock, flags); + ret = rpmsg_send(cctx->rpdev->ept, (void *)msg, sizeof(*msg)); - if (ret) - fastrpc_context_put(ctx); + if (ret) { + spin_lock_irqsave(&cctx->lock, flags); + if (ctx->completed) + put_ref = false; + else + ctx->sent = false; + spin_unlock_irqrestore(&cctx->lock, flags); + + if (put_ref) + fastrpc_context_put(ctx); + } return ret; @@ -2586,6 +2603,10 @@ static int fastrpc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, spin_lock_irqsave(&cctx->lock, flags); ctx = idr_find(&cctx->ctx_idr, ctxid); + if (ctx && (!ctx->sent || ctx->completed)) + ctx = NULL; + if (ctx) + ctx->completed = true; spin_unlock_irqrestore(&cctx->lock, flags); if (!ctx) { -- 2.54.0
