490 lines
12 KiB
C
490 lines
12 KiB
C
|
/*
|
||
|
* Copyright (c) 2016-2018 NVIDIA Corporation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
* more details.
|
||
|
*
|
||
|
*/
|
||
|
#include <linux/completion.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/slab.h>
|
||
|
|
||
|
#include <linux/trusty/trusty.h>
|
||
|
#include <linux/trusty/trusty_ipc.h>
|
||
|
|
||
|
#include "trusty-ote.h"
|
||
|
|
||
|
/* Time out in milli seconds */
|
||
|
#define REPLY_TIMEOUT 5000
|
||
|
#define TXBUF_TIMEOUT 15000
|
||
|
|
||
|
//#define DEBUG
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
#define trusty_ote_debug(...) \
|
||
|
pr_err("trusty-ote: " __VA_ARGS__)
|
||
|
#else
|
||
|
#define trusty_ote_debug(...) \
|
||
|
({ \
|
||
|
if (0) \
|
||
|
pr_info("trusty-ote: " __VA_ARGS__); \
|
||
|
0; \
|
||
|
})
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Currently assumed that only one buffer is sent using these APIs
|
||
|
* per operation apart from header
|
||
|
*/
|
||
|
#define MAX_TIPC_MSG_NUM (1 + 1)
|
||
|
|
||
|
struct tipc_chan_ctx {
|
||
|
struct tipc_chan *chan;
|
||
|
struct completion reply_comp;
|
||
|
int state;
|
||
|
/*
|
||
|
* Stores the data that is sent/received over channel
|
||
|
*/
|
||
|
void *data[MAX_TIPC_MSG_NUM];
|
||
|
size_t len[MAX_TIPC_MSG_NUM];
|
||
|
int cur_msg;
|
||
|
int total_msg;
|
||
|
};
|
||
|
|
||
|
enum tipc_chan_state {
|
||
|
TIPC_DISCONNECTED = 0,
|
||
|
TIPC_CONNECTED,
|
||
|
TIPC_NOT_FOUND
|
||
|
};
|
||
|
|
||
|
static int wait_for_response(struct tipc_chan_ctx *chan_ctx, int timeout)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = wait_for_completion_interruptible_timeout(&chan_ctx->reply_comp,
|
||
|
msecs_to_jiffies(timeout));
|
||
|
if (ret > 0)
|
||
|
/* Received reply */
|
||
|
ret = 0;
|
||
|
else if (ret == 0)
|
||
|
/* No reply from remote */
|
||
|
ret = -ETIMEDOUT;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct tipc_msg_buf *_handle_msg(void *data, struct tipc_msg_buf *rxbuf)
|
||
|
{
|
||
|
struct tipc_chan_ctx *chan_ctx = (struct tipc_chan_ctx *)data;
|
||
|
|
||
|
trusty_ote_debug("%s\n", __func__);
|
||
|
if (mb_avail_data(rxbuf) != chan_ctx->len[chan_ctx->cur_msg]) {
|
||
|
pr_err("%s:ERROR: expected msg(%d) len %zu: actual %zu",
|
||
|
__func__, chan_ctx->cur_msg, chan_ctx->len[chan_ctx->cur_msg],
|
||
|
mb_avail_data(rxbuf));
|
||
|
return rxbuf;
|
||
|
}
|
||
|
|
||
|
/* copy data received over channel */
|
||
|
memcpy(chan_ctx->data[chan_ctx->cur_msg],
|
||
|
mb_get_data(rxbuf, chan_ctx->len[chan_ctx->cur_msg]),
|
||
|
chan_ctx->len[chan_ctx->cur_msg]);
|
||
|
|
||
|
chan_ctx->cur_msg++;
|
||
|
/* wake up client if all messages are received */
|
||
|
if (chan_ctx->cur_msg == chan_ctx->total_msg)
|
||
|
complete(&chan_ctx->reply_comp);
|
||
|
|
||
|
return rxbuf;
|
||
|
}
|
||
|
|
||
|
void handle_connect_event(struct tipc_chan_ctx *chan_ctx)
|
||
|
{
|
||
|
chan_ctx->state = TIPC_CONNECTED;
|
||
|
complete(&chan_ctx->reply_comp);
|
||
|
}
|
||
|
|
||
|
static void _handle_event(void *data, int event)
|
||
|
{
|
||
|
struct tipc_chan_ctx *chan_ctx = data;
|
||
|
|
||
|
trusty_ote_debug("%s: event %d state %d\n", __func__,
|
||
|
event, chan_ctx->state);
|
||
|
switch (event) {
|
||
|
case TIPC_CHANNEL_SHUTDOWN:
|
||
|
pr_err("%s: channel(state:%d) shutting down\n",
|
||
|
__func__, chan_ctx->state);
|
||
|
chan_ctx->state = TIPC_DISCONNECTED;
|
||
|
/* wake up the pending client */
|
||
|
complete(&chan_ctx->reply_comp);
|
||
|
break;
|
||
|
|
||
|
case TIPC_CHANNEL_DISCONNECTED:
|
||
|
pr_err("%s: channel(state:%d) disconnected\n",
|
||
|
__func__, chan_ctx->state);
|
||
|
chan_ctx->state = TIPC_DISCONNECTED;
|
||
|
/* wake up the pending client */
|
||
|
complete(&chan_ctx->reply_comp);
|
||
|
break;
|
||
|
|
||
|
case TIPC_CHANNEL_NOT_FOUND:
|
||
|
chan_ctx->state = TIPC_NOT_FOUND;
|
||
|
/* wake up the pending client */
|
||
|
complete(&chan_ctx->reply_comp);
|
||
|
break;
|
||
|
|
||
|
case TIPC_CHANNEL_CONNECTED:
|
||
|
handle_connect_event(chan_ctx);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
pr_err("%s: unhandled event %d\n", __func__, event);
|
||
|
break;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void _handle_release(void *data)
|
||
|
{
|
||
|
trusty_ote_debug("%s\n", __func__);
|
||
|
kfree(data);
|
||
|
}
|
||
|
|
||
|
struct tipc_chan_ops chan_ops = {
|
||
|
.handle_msg = _handle_msg,
|
||
|
.handle_event = _handle_event,
|
||
|
.handle_release = _handle_release,
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Embeds the data in TIPC buffer and queue the message
|
||
|
* to send to secure world.
|
||
|
*/
|
||
|
static int queue_msg(struct tipc_chan_ctx *chan_ctx, void *data, size_t len)
|
||
|
{
|
||
|
int ret;
|
||
|
struct tipc_msg_buf *txbuf = NULL;
|
||
|
|
||
|
trusty_ote_debug("%s: data %p len %zu\n", __func__, data, len);
|
||
|
|
||
|
txbuf = tipc_chan_get_txbuf_timeout(chan_ctx->chan, TXBUF_TIMEOUT);
|
||
|
if (IS_ERR(txbuf)) {
|
||
|
pr_err("%s:error(%ld) in get txbuf\n", __func__, PTR_ERR(txbuf));
|
||
|
return PTR_ERR(txbuf);
|
||
|
}
|
||
|
|
||
|
/* check available space */
|
||
|
if (len > mb_avail_space(txbuf)) {
|
||
|
ret = -EMSGSIZE;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
/* copy in message data */
|
||
|
memcpy(mb_put_data(txbuf, len), data, len);
|
||
|
|
||
|
/* queue message */
|
||
|
ret = tipc_chan_queue_msg(chan_ctx->chan, txbuf);
|
||
|
if (ret)
|
||
|
goto err;
|
||
|
|
||
|
return ret;
|
||
|
err:
|
||
|
tipc_chan_put_txbuf(chan_ctx->chan, txbuf);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int construct_default_stream_header(stream_header_t *stream_header) {
|
||
|
|
||
|
if (NULL == stream_header) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
memset(stream_header, 0, STREAM_META_HEADER_LEN);
|
||
|
|
||
|
stream_header->magic = STREAM_HEADER_MAGIC;
|
||
|
stream_header->version = STREAM_HEADER_CUR_VERSION;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int construct_default_payload_header(payload_header_t *payload_header) {
|
||
|
|
||
|
if (NULL == payload_header) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
memset(payload_header, 0, PAYLOAD_META_HEADER_LEN);
|
||
|
|
||
|
payload_header->magic = PAYLOAD_HEADER_MAGIC;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Constructs OTE message and sent it to TA over TIPC channel.
|
||
|
* And then wait till response is received from TA.
|
||
|
*/
|
||
|
static int handle_ote_msg(struct tipc_chan_ctx *chan_ctx, void *buf,
|
||
|
size_t len, uint32_t cmd, te_error_t *op_status)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
stream_header_t stream_header;
|
||
|
payload_header_t payload_header;
|
||
|
uint8_t *payload_buffer = NULL;
|
||
|
|
||
|
if (NULL == chan_ctx) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (len > TIPC_MAX_CHUNK_SIZE) {
|
||
|
pr_err("%s: passing buffers of size > %u not supported. len(%zu)"
|
||
|
, __func__, TIPC_MAX_CHUNK_SIZE, len);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = construct_default_stream_header(&stream_header);
|
||
|
if (ret < 0) {
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
stream_header.command = cmd;
|
||
|
|
||
|
trusty_ote_debug("%s: buf %p len %zu\n", __func__, buf, len);
|
||
|
|
||
|
if (NULL != buf) {
|
||
|
stream_header.num_entries = 1;
|
||
|
stream_header.total_length = PAYLOAD_META_HEADER_LEN + len;
|
||
|
|
||
|
ret = construct_default_payload_header(&payload_header);
|
||
|
if (ret < 0) {
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
payload_header.type = TE_PARAM_TYPE_MEM_RW;
|
||
|
payload_header.length = len;
|
||
|
|
||
|
payload_buffer = kzalloc((len + PAYLOAD_META_HEADER_LEN), GFP_KERNEL);
|
||
|
if (NULL == payload_buffer) {
|
||
|
ret = -ENOMEM;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
memcpy(payload_buffer, &payload_header, PAYLOAD_META_HEADER_LEN);
|
||
|
memcpy(&payload_buffer[PAYLOAD_META_HEADER_LEN], buf, len);
|
||
|
}
|
||
|
|
||
|
chan_ctx->cur_msg = 0;
|
||
|
chan_ctx->total_msg = 1;
|
||
|
chan_ctx->data[0] = &stream_header;
|
||
|
chan_ctx->len[0] = STREAM_META_HEADER_LEN;
|
||
|
if (NULL != buf) {
|
||
|
chan_ctx->data[1] = payload_buffer;
|
||
|
chan_ctx->len[1] = len + PAYLOAD_META_HEADER_LEN;
|
||
|
chan_ctx->total_msg++;
|
||
|
}
|
||
|
|
||
|
/* queue stream header */
|
||
|
trusty_ote_debug("%s: queue ote header\n", __func__);
|
||
|
ret = queue_msg(chan_ctx, chan_ctx->data[0], chan_ctx->len[0]);
|
||
|
if (ret) {
|
||
|
pr_err("%s:error(%d) in queue header\n", __func__, ret);
|
||
|
goto err_exit;
|
||
|
}
|
||
|
|
||
|
/* queue payload if present */
|
||
|
if (NULL != buf) {
|
||
|
trusty_ote_debug("%s: queue payload\n", __func__);
|
||
|
ret = queue_msg(chan_ctx, chan_ctx->data[1], chan_ctx->len[1]);
|
||
|
if (ret) {
|
||
|
pr_err("%s:error(%d) in queue payload\n", __func__, ret);
|
||
|
goto err_exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
trusty_ote_debug("%s: waiting for response\n", __func__);
|
||
|
ret = wait_for_response(chan_ctx, REPLY_TIMEOUT);
|
||
|
if (ret < 0) {
|
||
|
pr_err("%s:ERROR(%d) in receiving header\n", __func__, ret);
|
||
|
goto err_exit;
|
||
|
}
|
||
|
|
||
|
/* sanity check */
|
||
|
WARN_ON(chan_ctx->state != TIPC_CONNECTED);
|
||
|
|
||
|
if (NULL != buf) {
|
||
|
memcpy(&payload_header, payload_buffer, PAYLOAD_META_HEADER_LEN);
|
||
|
if (payload_header.magic != PAYLOAD_HEADER_MAGIC) {
|
||
|
ret = -EINVAL;
|
||
|
pr_err("%s: payload header magic mismatch. received != expected "
|
||
|
"(%x != %x)\n", __func__, payload_header.magic,
|
||
|
PAYLOAD_HEADER_MAGIC);
|
||
|
goto err_exit;
|
||
|
}
|
||
|
memcpy(buf, &payload_buffer[PAYLOAD_META_HEADER_LEN], len);
|
||
|
}
|
||
|
|
||
|
*op_status = stream_header.status;
|
||
|
trusty_ote_debug("%s: op status 0x%08x\n", __func__, *op_status);
|
||
|
|
||
|
err_exit:
|
||
|
kfree(payload_buffer);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* te_open_trusted_session - Establishes the session with TA
|
||
|
* @name(in): name of the TA to connect to.
|
||
|
* @ctx(out): pointer to the private data associated to the open session
|
||
|
* Returns 0 on Success else error code.
|
||
|
*/
|
||
|
int te_open_trusted_session(char *name, void **ctx)
|
||
|
{
|
||
|
int ret;
|
||
|
te_error_t op_status = OTE_ERROR_GENERIC;
|
||
|
struct tipc_chan_ctx *chan_ctx;
|
||
|
|
||
|
trusty_ote_debug("%s: service %s\n", __func__, name);
|
||
|
|
||
|
if (!ctx || !name)
|
||
|
return -EINVAL;
|
||
|
|
||
|
chan_ctx = kzalloc(sizeof(struct tipc_chan_ctx), GFP_KERNEL);
|
||
|
if (!chan_ctx) {
|
||
|
ret = -ENOMEM;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
chan_ctx->chan = tipc_create_channel(NULL, &chan_ops, chan_ctx);
|
||
|
if(IS_ERR(chan_ctx->chan)) {
|
||
|
ret = PTR_ERR(chan_ctx->chan);
|
||
|
pr_err("%s:ERROR(%d) in tipc_create_channel\n", __func__, ret);
|
||
|
goto err_chan;
|
||
|
}
|
||
|
|
||
|
init_completion(&chan_ctx->reply_comp);
|
||
|
chan_ctx->state = TIPC_DISCONNECTED;
|
||
|
|
||
|
ret = tipc_chan_connect(chan_ctx->chan, name);
|
||
|
if (ret) {
|
||
|
pr_err("%s:ERROR(%d) in tipc_chan_connect\n", __func__, ret);
|
||
|
goto err_conn;
|
||
|
}
|
||
|
|
||
|
ret = wait_for_response(chan_ctx, REPLY_TIMEOUT);
|
||
|
if (ret < 0) {
|
||
|
pr_err("%s:ERROR(%d) in receiving response from service\n",
|
||
|
__func__, ret);
|
||
|
goto err_conn;
|
||
|
}
|
||
|
|
||
|
if (chan_ctx->state == TIPC_NOT_FOUND) {
|
||
|
ret = -EOPNOTSUPP;
|
||
|
goto err_conn;
|
||
|
}
|
||
|
|
||
|
if (chan_ctx->state != TIPC_CONNECTED) {
|
||
|
pr_err("%s:Invalid channel state %d\n",
|
||
|
__func__, chan_ctx->state);
|
||
|
ret = -ENOTCONN;
|
||
|
goto err_conn;
|
||
|
}
|
||
|
|
||
|
ret = handle_ote_msg(chan_ctx, NULL, 0, 0, &op_status);
|
||
|
if (ret) {
|
||
|
pr_err("%s:ERROR(%d) in handle_ote_msg\n", __func__, ret);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (op_status) {
|
||
|
pr_err("%s: ERROR in operation 0x%08x", __func__, op_status);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
*ctx = chan_ctx;
|
||
|
return 0;
|
||
|
|
||
|
err:
|
||
|
tipc_chan_shutdown(chan_ctx->chan);
|
||
|
err_conn:
|
||
|
tipc_chan_destroy(chan_ctx->chan);
|
||
|
/* chan_ctx is freed in a callback, no need to explicitly free it here */
|
||
|
return ret;
|
||
|
|
||
|
err_chan:
|
||
|
kfree(chan_ctx);
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(te_open_trusted_session);
|
||
|
|
||
|
/*
|
||
|
* te_close_trusted_session - Closes the session established
|
||
|
* @ctx: ctx returned by open session
|
||
|
*/
|
||
|
void te_close_trusted_session(void *ctx)
|
||
|
{
|
||
|
struct tipc_chan_ctx *chan_ctx;
|
||
|
|
||
|
trusty_ote_debug("%s \n", __func__);
|
||
|
if (!ctx)
|
||
|
return;
|
||
|
|
||
|
chan_ctx = (struct tipc_chan_ctx *)ctx;
|
||
|
|
||
|
/* wake up the pending client */
|
||
|
complete(&chan_ctx->reply_comp);
|
||
|
|
||
|
if (chan_ctx->state == TIPC_CONNECTED)
|
||
|
tipc_chan_shutdown(chan_ctx->chan);
|
||
|
tipc_chan_destroy(chan_ctx->chan);
|
||
|
}
|
||
|
EXPORT_SYMBOL(te_close_trusted_session);
|
||
|
|
||
|
/*
|
||
|
* te_launch_trusted_oper - Communicate with TA to perform any operation
|
||
|
* @buf: Buffer to be sent to secure world (NULL if a buffer is not required)
|
||
|
* @buf_len: length of the buffer. (0 if a buffer is not required)
|
||
|
* @ta_cmd: command to sent to secure world.
|
||
|
* @ctx: ctx returned by open session.
|
||
|
* Returns 0 on Success else error code.
|
||
|
*/
|
||
|
int te_launch_trusted_oper(void *buf, size_t buf_len, uint32_t ta_cmd,
|
||
|
void *ctx)
|
||
|
{
|
||
|
int ret;
|
||
|
te_error_t op_status = OTE_ERROR_GENERIC;
|
||
|
struct tipc_chan_ctx *chan_ctx;
|
||
|
|
||
|
trusty_ote_debug("%s: cmd %u\n", __func__, ta_cmd);
|
||
|
|
||
|
if (!ctx)
|
||
|
return -EINVAL;
|
||
|
|
||
|
chan_ctx = (struct tipc_chan_ctx *)ctx;
|
||
|
|
||
|
if (chan_ctx->state != TIPC_CONNECTED) {
|
||
|
pr_err("%s:Invalid channel state %d\n",
|
||
|
__func__, chan_ctx->state);
|
||
|
return -ENOTCONN;
|
||
|
}
|
||
|
|
||
|
ret = handle_ote_msg(chan_ctx, buf, buf_len, ta_cmd, &op_status);
|
||
|
if (ret) {
|
||
|
pr_err("%s:ERROR(%d) in handle_ote_msg\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (op_status) {
|
||
|
pr_err("%s: ERROR in operation 0x%08x", __func__, op_status);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(te_launch_trusted_oper);
|