blob: ac102b69406c9a92013a48f9c14f438638d720ba [file] [log] [blame]
/*
* Copyright (c) 2021, Google Inc. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include <err.h>
#include <interface/metrics/consumer.h>
#include <kernel/mutex.h>
#include <lib/dpc.h>
#include <lib/trusty/handle.h>
#include <lib/trusty/ipc.h>
#include <lib/trusty/ipc_msg.h>
#include <lib/trusty/trusty_app.h>
#include <lk/init.h>
#include <lk/trace.h>
#include <string.h>
#include <trusty/uuid.h>
#define LOCAL_TRACE (0)
/*
* Format of the payload is "<UUID>:<app name>", with neither UUID nor app name
* being null-terminated. However, unlike APP_NAME_MAX_SIZE, UUID_STR_SIZE
* counts the null character. Hence, the maximum size of an app name is
* METRICS_MAX_APP_ID_LEN - UUID_STR_SIZE.
*/
static_assert(UUID_STR_SIZE <= METRICS_MAX_APP_ID_LEN);
#define APP_NAME_MAX_SIZE (METRICS_MAX_APP_ID_LEN - UUID_STR_SIZE)
/**
* enum chan_state - states of the metrics consumer channel event handler
* CHAN_STATE_WAITING_CHAN_READY:
* Inital state of the channel handler. At this point we are waiting for an
* IPC_HANDLE_POLL_READY channel event that signifies that metrics consumer
* connection is ready for use. After consuming this event, we transition
* to %CHAN_STATE_IDLE state.
* CHAN_STATE_IDLE:
* While in this state we (2) can not consume any events from the channel
* (1) can only send one message over the channel. Once a message is sent,
* we transition to either %CHAN_STATE_WAITING_CRASH_RESP or
* %CHAN_STATE_WAITING_EVENT_DROP_RESP depending on what message was sent.
* CHAN_STATE_WAITING_CRASH_RESP:
* In this state we are waiting for a response to a message about an app
* crash. After receiving the response message, we transition to
* %CHAN_STATE_IDLE state.
* CHAN_STATE_WAITING_EVENT_DROP_RESP:
* In this state we are waiting for a response to a message about an event
* drop. After receiving the response message, we transition to
* %CHAN_STATE_IDLE state.
*/
enum chan_state {
CHAN_STATE_WAITING_CHAN_READY = 0,
CHAN_STATE_IDLE = 1,
CHAN_STATE_WAITING_CRASH_RESP = 2,
CHAN_STATE_WAITING_EVENT_DROP_RESP = 3,
};
struct metrics_ctx {
struct handle* chan;
enum chan_state chan_state;
bool event_dropped;
};
static struct metrics_ctx ctx;
static mutex_t ctx_lock = MUTEX_INITIAL_VALUE(ctx_lock);
static int recv_resp(struct handle* chan, uint32_t cmd) {
int rc;
struct ipc_msg_info msg_info;
struct metrics_resp resp;
rc = ipc_get_msg(chan, &msg_info);
if (rc != NO_ERROR) {
TRACEF("failed (%d) to get message\n", rc);
return rc;
}
struct iovec_kern iov = {
.iov_base = &resp,
.iov_len = sizeof(resp),
};
struct ipc_msg_kern ipc_msg = {
.num_iov = 1,
.iov = &iov,
.num_handles = 0,
.handles = NULL,
};
rc = ipc_read_msg(chan, msg_info.id, 0, &ipc_msg);
ipc_put_msg(chan, msg_info.id);
if (rc < 0) {
TRACEF("failed (%d) ipc_read_msg().\n", rc);
return rc;
}
if (rc != sizeof(resp)) {
TRACEF("unexpected number of bytes received: %d.\n", rc);
return ERR_BAD_LEN;
}
if (resp.cmd != (cmd | METRICS_CMD_RESP_BIT)) {
TRACEF("unknown command received: %u.\n", resp.cmd);
return ERR_CMD_UNKNOWN;
}
if (resp.status != METRICS_NO_ERROR) {
TRACEF("event report failure: %d.\n", resp.status);
/* This error is not severe enough to close the connection. */
}
return NO_ERROR;
}
static int send_req(struct handle* chan,
struct ipc_msg_kern* ipc_msg,
size_t total_len) {
int rc = ipc_send_msg(chan, ipc_msg);
if (rc < 0) {
TRACEF("failed (%d) to send message\n", rc);
return rc;
}
if (rc != (int)total_len) {
TRACEF("unexpected number of bytes sent: %d\n", rc);
return ERR_BAD_LEN;
}
return NO_ERROR;
}
static int report_crash(struct handle* chan,
struct trusty_app* app,
uint32_t crash_reason) {
int rc;
struct metrics_req req = {};
struct metrics_report_crash_req args = {};
size_t total_len;
DEBUG_ASSERT(is_mutex_held(&ctx_lock));
uuid_to_str(&app->props.uuid, args.app_id);
req.cmd = METRICS_CMD_REPORT_CRASH;
args.crash_reason = crash_reason;
struct iovec_kern iovs[] = {
{
.iov_base = &req,
.iov_len = sizeof(req),
},
{
.iov_base = &args,
.iov_len = sizeof(args),
},
};
struct ipc_msg_kern ipc_msg = {
.num_iov = countof(iovs),
.iov = iovs,
};
total_len = sizeof(req) + sizeof(args);
rc = send_req(chan, &ipc_msg, total_len);
if (rc != NO_ERROR) {
TRACEF("failed (%d) report app crash\n", rc);
return rc;
}
return NO_ERROR;
}
static int report_event_drop(struct handle* chan) {
int rc;
struct metrics_req req;
DEBUG_ASSERT(is_mutex_held(&ctx_lock));
req.cmd = METRICS_CMD_REPORT_EVENT_DROP;
req.reserved = 0;
struct iovec_kern iov = {
.iov_base = &req,
.iov_len = sizeof(req),
};
struct ipc_msg_kern ipc_msg = {
.num_iov = 1,
.iov = &iov,
};
rc = send_req(chan, &ipc_msg, sizeof(req));
if (rc != NO_ERROR) {
TRACEF("failed (%d) report event drop\n", rc);
return rc;
}
return NO_ERROR;
}
static int on_ta_crash(struct trusty_app* app, uint32_t reason) {
int rc;
mutex_acquire(&ctx_lock);
if (ctx.chan_state != CHAN_STATE_IDLE) {
TRACEF("there is a metrics event still in progress or metrics TA "
"is unavailable\n");
ctx.event_dropped = true;
goto out;
}
if (!ctx.chan) {
TRACEF("failed get metrics consumer channel\n");
goto out;
}
rc = report_crash(ctx.chan, app, reason);
if (rc != NO_ERROR) {
TRACEF("failed (%d) report app crash\n", rc);
goto err;
}
ctx.chan_state = CHAN_STATE_WAITING_CRASH_RESP;
goto out;
err:
handle_close(ctx.chan);
ctx.chan = NULL;
out:
mutex_release(&ctx_lock);
/*
* Returning an error here will bring down the kernel. Metrics reporting
* isn't critical. So, we always return NO_ERROR. If something goes wrong,
* printing an error should suffice.
*/
return NO_ERROR;
}
static struct trusty_app_notifier notifier = {
.crash = on_ta_crash,
};
static void handle_chan(struct dpc* work) {
int rc;
uint32_t event;
mutex_acquire(&ctx_lock);
event = ctx.chan->ops->poll(ctx.chan, ~0U, true);
if (event & IPC_HANDLE_POLL_HUP) {
TRACEF("received IPC_HANDLE_POLL_HUP, closing channel\n");
goto err;
}
switch (ctx.chan_state) {
case CHAN_STATE_WAITING_CHAN_READY:
if (!(event & IPC_HANDLE_POLL_READY)) {
TRACEF("unexpected channel event: 0x%x\n", event);
goto err;
}
ctx.chan_state = CHAN_STATE_IDLE;
goto out;
case CHAN_STATE_IDLE:
TRACEF("unexpected channel event: 0x%x\n", event);
goto err;
case CHAN_STATE_WAITING_CRASH_RESP:
if (!(event & IPC_HANDLE_POLL_MSG)) {
TRACEF("unexpected channel event: 0x%x\n", event);
goto err;
}
rc = recv_resp(ctx.chan, METRICS_CMD_REPORT_CRASH);
if (rc != NO_ERROR) {
TRACEF("failed (%d) receive response\n", rc);
goto err;
}
ctx.chan_state = CHAN_STATE_IDLE;
if (ctx.event_dropped) {
rc = report_event_drop(ctx.chan);
if (rc != NO_ERROR) {
TRACEF("failed (%d) report event drop\n", rc);
goto err;
}
ctx.chan_state = CHAN_STATE_WAITING_EVENT_DROP_RESP;
goto out;
}
goto out;
case CHAN_STATE_WAITING_EVENT_DROP_RESP:
if (!(event & IPC_HANDLE_POLL_MSG)) {
TRACEF("unexpected channel event: 0x%x\n", event);
goto err;
}
rc = recv_resp(ctx.chan, METRICS_CMD_REPORT_EVENT_DROP);
if (rc != NO_ERROR) {
TRACEF("failed (%d) receive response\n", rc);
goto err;
}
ctx.chan_state = CHAN_STATE_IDLE;
ctx.event_dropped = false;
goto out;
}
err:
handle_close(ctx.chan);
ctx.chan = NULL;
out:
mutex_release(&ctx_lock);
}
static struct dpc chan_event_work = {
.node = LIST_INITIAL_CLEARED_VALUE,
.cb = handle_chan,
};
static void on_handle_event(struct handle_waiter* waiter) {
int rc = dpc_enqueue_work(NULL, &chan_event_work, false);
if (rc != NO_ERROR) {
TRACEF("failed (%d) to enqueue dpc work\n", rc);
}
}
static struct handle_waiter waiter = {
.node = LIST_INITIAL_CLEARED_VALUE,
.notify_proc = on_handle_event,
};
static void metrics_init(uint level) {
int rc = ipc_port_connect_async(&kernel_uuid, METRICS_CONSUMER_PORT,
IPC_PORT_PATH_MAX,
IPC_CONNECT_WAIT_FOR_PORT, &ctx.chan);
if (rc) {
TRACEF("failed (%d) to connect to port\n", rc);
goto err_port_connect;
}
rc = trusty_register_app_notifier(&notifier);
if (rc) {
TRACEF("failed (%d) to register app notifier\n", rc);
goto err_app_notifier;
}
ctx.chan_state = CHAN_STATE_WAITING_CHAN_READY;
handle_add_waiter(ctx.chan, &waiter);
return;
err_app_notifier:
handle_close(ctx.chan);
ctx.chan = NULL;
err_port_connect:
return;
}
/* Need to init before (LK_INIT_LEVEL_APPS - 1) to register an app notifier. */
LK_INIT_HOOK(metrics, metrics_init, LK_INIT_LEVEL_APPS - 2);