blob: 9419060244e7d296516daa560c9aeb1f43541a9d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Google LLC
*/
#include "gsa_tz.h"
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#define TZ_CON_TIMEOUT 5000
#define TZ_MSG_TIMEOUT 10000
#define TZ_BUF_TIMEOUT 10000
static struct tipc_msg_buf *tz_srv_handle_msg(void *data,
struct tipc_msg_buf *rxbuf)
{
struct gsa_tz_chan_ctx *ctx = data;
/* copy out reply then signal completion */
mutex_lock(&ctx->rsp_lock);
if (ctx->rsp_buf) {
size_t len = mb_avail_data(rxbuf);
if (len <= ctx->rsp_buf_size) {
memcpy(ctx->rsp_buf, mb_get_data(rxbuf, len), len);
ctx->rsp_res = len;
} else {
dev_err(ctx->dev,
"TZ: RSP buffer is too small (%zd vs. %zd)\n",
ctx->rsp_buf_size, len);
ctx->rsp_res = -EMSGSIZE;
}
} else {
dev_err(ctx->dev, "TZ: no RSP buffer: drop message\n");
}
mutex_unlock(&ctx->rsp_lock);
complete(&ctx->reply_comp);
return rxbuf;
}
static void tz_srv_handle_event(void *data, int event)
{
struct gsa_tz_chan_ctx *ctx = data;
complete(&ctx->reply_comp);
}
static const struct tipc_chan_ops tz_srv_ops = {
.handle_msg = tz_srv_handle_msg,
.handle_event = tz_srv_handle_event,
};
static int tz_srv_connect_locked(struct gsa_tz_chan_ctx *ctx)
{
int rc;
unsigned long timeout;
struct tipc_chan *chan;
chan = tipc_create_channel(NULL, &tz_srv_ops, ctx);
if (IS_ERR(chan)) {
dev_err(ctx->dev, "TZ: failed (%ld) to create chan\n",
PTR_ERR(chan));
return PTR_ERR(chan);
}
reinit_completion(&ctx->reply_comp);
rc = tipc_chan_connect(chan, ctx->port);
if (rc < 0) {
dev_err(ctx->dev, "TZ: failed (%d) to connect\n", rc);
goto err_connect;
}
/* wait for connection */
timeout = msecs_to_jiffies(TZ_CON_TIMEOUT);
rc = wait_for_completion_timeout(&ctx->reply_comp, timeout);
if (rc <= 0) {
rc = (!rc) ? -ETIMEDOUT : rc;
dev_err(ctx->dev, "TZ: failed (%d) to wait for connect\n", rc);
goto err_reply;
}
/* Note: we will check connection state while sending message */
ctx->chan = chan;
return 0;
err_reply:
tipc_chan_shutdown(chan);
err_connect:
tipc_chan_destroy(chan);
return rc;
};
static void gsa_tz_chan_close_locked(struct gsa_tz_chan_ctx *ctx)
{
if (ctx->chan) {
tipc_chan_shutdown(ctx->chan);
tipc_chan_destroy(ctx->chan);
ctx->chan = NULL;
}
}
int gsa_tz_chan_msg_xchg(struct gsa_tz_chan_ctx *ctx,
const void *req, size_t req_len,
void *rsp, size_t rsp_size)
{
int rc;
unsigned long timeout;
struct tipc_msg_buf *txbuf;
unsigned int retry_connect = 1;
/* we need both request and response */
if (!req || !rsp) {
return -EINVAL;
}
mutex_lock(&ctx->req_lock);
reconnect:
if (!ctx->chan) {
/* connect to TZ service */
rc = tz_srv_connect_locked(ctx);
if (rc < 0) {
dev_err(ctx->dev, "TZ: failed (%d) to connect\n", rc);
goto err_connect;
}
dev_info(ctx->dev, "TZ: %s connected\n", ctx->port);
}
/* get tx buffer */
txbuf = tipc_chan_get_txbuf_timeout(ctx->chan, TZ_BUF_TIMEOUT);
if (IS_ERR(txbuf)) {
dev_err(ctx->dev,
"TZ: failed (%ld) to get txbuf\n", PTR_ERR(txbuf));
rc = PTR_ERR(txbuf);
goto err_get_buf;
}
/* copy in request */
memcpy(mb_put_data(txbuf, req_len), req, sizeof(req_len));
/* attach buffer to store response */
mutex_lock(&ctx->rsp_lock);
ctx->rsp_buf = rsp;
ctx->rsp_buf_size = rsp_size;
ctx->rsp_res = -ENOMSG;
mutex_unlock(&ctx->rsp_lock);
/* init completion */
reinit_completion(&ctx->reply_comp);
/* queue message */
rc = tipc_chan_queue_msg(ctx->chan, txbuf);
if (rc < 0) {
dev_err(ctx->dev, "TZ: failed (%d) to queue msg\n", rc);
/* drop buffer */
tipc_chan_put_txbuf(ctx->chan, txbuf);
/* reset connection */
gsa_tz_chan_close_locked(ctx);
/*
* It is possible that server has closed connection
* and we might be able to recover
*/
if (retry_connect--) {
dev_info(ctx->dev, "TZ: reconnect\n");
goto reconnect;
}
goto err_queue;
}
/* wait for response */
timeout = msecs_to_jiffies(TZ_MSG_TIMEOUT);
rc = wait_for_completion_timeout(&ctx->reply_comp, timeout);
if (rc <= 0) {
rc = (!rc) ? -ETIMEDOUT : rc;
dev_err(ctx->dev, "TZ: failed (%d) to wait for reply\n", rc);
/* reset connection */
gsa_tz_chan_close_locked(ctx);
/* bail, it is unlikely that reconnect would solve it */
}
err_queue:
err_get_buf:
err_connect:
/* detach response buffer and return response result */
mutex_lock(&ctx->rsp_lock);
ctx->rsp_buf = NULL;
ctx->rsp_buf_size = 0;
if (rc > 0) {
/* return response result */
rc = ctx->rsp_res;
}
mutex_unlock(&ctx->rsp_lock);
/* release request lock */
mutex_unlock(&ctx->req_lock);
return rc;
}
void gsa_tz_chan_close(struct gsa_tz_chan_ctx *ctx)
{
mutex_lock(&ctx->req_lock);
gsa_tz_chan_close_locked(ctx);
mutex_unlock(&ctx->req_lock);
}
void gsa_tz_chan_ctx_init(struct gsa_tz_chan_ctx *ctx, const char *port,
struct device *dev)
{
ctx->dev = dev;
strlcpy(ctx->port, port, sizeof(ctx->port));
mutex_init(&ctx->req_lock);
mutex_init(&ctx->rsp_lock);
init_completion(&ctx->reply_comp);
ctx->rsp_buf = NULL;
ctx->rsp_buf_size = 0;
ctx->rsp_res = 0;
}