kernel-brax3-ubuntu-touch/drivers/tee/gud/510/MobiCoreDriver/mcp.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

924 lines
23 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2013-2020 TRUSTONIC LIMITED
* 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
* version 2 as published by the Free Software Foundation.
*
* 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/err.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/completion.h>
#include <linux/circ_buf.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/freezer.h>
#include <asm/barrier.h>
#include <linux/irq.h>
#include <linux/jiffies.h>
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/clock.h> /* local_clock */
#endif
#include "public/mc_user.h"
#include "public/mc_admin.h"
#include "mci/mcimcp.h"
#include "mci/mcifc.h"
#include "mci/mcinq.h" /* SID_MCP */
#include "mci/mcitime.h" /* struct mcp_time */
#include "mci/mciiwp.h"
#include "main.h"
#include "mmu.h" /* MMU for ta and tci */
#include "nq.h"
#include "protocol_common.h"
#include "mcp.h"
/* respond timeout for MCP notification, in secs */
#define MCP_TIMEOUT 10
#define MCP_RETRIES 5
#define MCP_NF_QUEUE_SZ 8
static struct {
union mcp_message *buffer; /* MCP communication buffer */
struct mutex buffer_mutex; /* Lock for the buffer above */
bool mcp_dead;
struct mcp_session mcp_session; /* Pseudo session for MCP */
/* Unexpected notification (during MCP open) */
struct mutex unexp_notif_mutex;
struct notification unexp_notif;
/* Sessions */
struct mutex sessions_lock;
struct list_head sessions;
/* TEE bad state detection */
struct notifier_block tee_stop_notifier;
u32 timeout_period;
/* Log of last commands */
#define LAST_CMDS_SIZE 1024
struct mutex last_cmds_mutex; /* Log protection */
struct command_info {
u64 cpu_clk; /* Kernel time */
pid_t pid; /* Caller PID */
enum cmd_id id; /* MCP command ID */
u32 session_id;
char uuid_str[34];
enum state {
UNUSED, /* Unused slot */
PENDING, /* Previous command in progress */
SENT, /* Waiting for response */
COMPLETE, /* Got result */
FAILED, /* Something went wrong */
} state; /* Command processing state */
int errno; /* Return code */
enum mcp_result result; /* Command result */
} last_cmds[LAST_CMDS_SIZE];
int last_cmds_index;
} l_ctx;
static const char *cmd_to_string(enum cmd_id id)
{
switch (id) {
case MC_MCP_CMD_ID_INVALID:
return "invalid";
case MC_MCP_CMD_OPEN_SESSION:
return "open session";
case MC_MCP_CMD_CLOSE_SESSION:
return "close session";
case MC_MCP_CMD_MAP:
return "map";
case MC_MCP_CMD_UNMAP:
return "unmap";
case MC_MCP_CMD_GET_MOBICORE_VERSION:
return "get version";
case MC_MCP_CMD_CLOSE_MCP:
return "close mcp";
case MC_MCP_CMD_LOAD_TOKEN:
return "load token";
}
return "unknown";
}
static const char *state_to_string(enum mcp_session_state state)
{
switch (state) {
case MCP_SESSION_RUNNING:
return "running";
case MCP_SESSION_CLOSE_FAILED:
return "close failed";
case MCP_SESSION_CLOSED:
return "closed";
}
return "error";
}
static inline void mark_mcp_dead(void)
{
struct mcp_session *session;
l_ctx.mcp_dead = true;
complete(&l_ctx.mcp_session.completion);
/* Signal all potential waiters that SWd is going away */
list_for_each_entry(session, &l_ctx.sessions, list)
complete(&session->completion);
}
static int tee_stop_notifier_fn(struct notifier_block *nb, unsigned long event,
void *data)
{
mark_mcp_dead();
return 0;
}
void mcp_session_init(struct mcp_session *session)
{
nq_session_init(&session->nq_session, false);
session->sid = SID_INVALID;
INIT_LIST_HEAD(&session->list);
mutex_init(&session->notif_wait_lock);
init_completion(&session->completion);
mutex_init(&session->exit_code_lock);
session->exit_code = 0;
session->state = MCP_SESSION_RUNNING;
session->notif_count = 0;
}
static inline bool mcp_session_isrunning(struct mcp_session *session)
{
bool ret;
mutex_lock(&l_ctx.sessions_lock);
ret = session->state == MCP_SESSION_RUNNING;
mutex_unlock(&l_ctx.sessions_lock);
return ret;
}
/*
* session remains valid thanks to the upper layers reference counters, but the
* SWd session may have died, in which case we are informed.
*/
int mcp_wait(struct mcp_session *session, s32 timeout)
{
s32 err = 0;
int ret = 0;
mutex_lock(&session->notif_wait_lock);
if (protocol_is_fe()) {
ret = protocol_mc_wait(session, timeout);
mutex_unlock(&session->notif_wait_lock);
return ret;
}
if (l_ctx.mcp_dead) {
ret = -EHOSTUNREACH;
goto end;
}
if (!mcp_session_isrunning(session)) {
ret = -ENXIO;
goto end;
}
mcp_get_err(session, &err);
if (err) {
ret = -ECOMM;
goto end;
}
if (timeout < 0) {
ret = wait_for_completion_interruptible(&session->completion);
if (ret)
goto end;
} else {
ret = wait_for_completion_interruptible_timeout(
&session->completion, timeout * HZ / 1000);
if (ret < 0)
/* Interrupted */
goto end;
if (!ret) {
/* Timed out */
ret = -ETIME;
goto end;
}
ret = 0;
}
if (l_ctx.mcp_dead) {
ret = -EHOSTUNREACH;
goto end;
}
mcp_get_err(session, &err);
if (err) {
ret = -ECOMM;
goto end;
}
if (!mcp_session_isrunning(session)) {
ret = -ENXIO;
goto end;
}
end:
if (!ret)
nq_session_state_update(&session->nq_session,
NQ_NOTIF_CONSUMED);
else if (ret != -ERESTARTSYS)
nq_session_state_update(&session->nq_session, NQ_NOTIF_DEAD);
mutex_unlock(&session->notif_wait_lock);
if (ret) {
#ifdef CONFIG_FREEZER
if (ret == -ERESTARTSYS && system_freezing_cnt.counter == 1)
mc_dev_devel("freezing session %x", session->sid);
else
#endif
mc_dev_devel("session %x ec %d ret %d",
session->sid, session->exit_code, ret);
}
return ret;
}
int mcp_get_err(struct mcp_session *session, s32 *err)
{
if (protocol_is_fe())
return protocol_mc_get_err(session, err);
mutex_lock(&session->exit_code_lock);
*err = session->exit_code;
mutex_unlock(&session->exit_code_lock);
if (*err)
mc_dev_info("session %x ec %d", session->sid, *err);
return 0;
}
static inline int wait_mcp_notification(void)
{
unsigned long timeout = msecs_to_jiffies(l_ctx.timeout_period * 1000);
int try, ret = -ETIME;
/*
* Total timeout is l_ctx.timeout_period * MCP_RETRIES, but we check for
* a crash to try and terminate before then if things go wrong.
*/
for (try = 1; try <= MCP_RETRIES; try++) {
/*
* Wait non-interruptible to keep MCP synchronised even if
* caller is interrupted by signal.
*/
if (wait_for_completion_timeout(&l_ctx.mcp_session.completion,
timeout) > 0)
return 0;
mc_dev_err(ret, "no answer after %ds",
l_ctx.timeout_period * try);
}
mc_dev_err(ret, "timed out waiting for MCP notification");
nq_signal_tee_hung();
return ret;
}
static int mcp_cmd(union mcp_message *cmd,
/* The fields below are for debug purpose only */
u32 in_session_id,
u32 *out_session_id,
struct mc_uuid_t *uuid)
{
int err = 0, ret = -EHOSTUNREACH;
union mcp_message *msg;
enum cmd_id cmd_id = cmd->cmd_header.cmd_id;
struct command_info *cmd_info;
/* Initialize MCP log */
mutex_lock(&l_ctx.last_cmds_mutex);
cmd_info = &l_ctx.last_cmds[l_ctx.last_cmds_index];
cmd_info->cpu_clk = local_clock();
cmd_info->pid = current->pid;
cmd_info->id = cmd_id;
cmd_info->session_id = in_session_id;
if (uuid) {
/* Keep UUID because it's an 'open session' cmd */
size_t i;
cmd_info->uuid_str[0] = ' ';
for (i = 0; i < sizeof(uuid->value); i++) {
snprintf(&cmd_info->uuid_str[1 + i * 2], 3, "%02x",
uuid->value[i]);
}
} else {
cmd_info->uuid_str[0] = '\0';
}
cmd_info->state = PENDING;
cmd_info->errno = 0;
cmd_info->result = MC_MCP_RET_OK;
if (++l_ctx.last_cmds_index >= LAST_CMDS_SIZE)
l_ctx.last_cmds_index = 0;
mutex_unlock(&l_ctx.last_cmds_mutex);
mutex_lock(&l_ctx.buffer_mutex);
msg = l_ctx.buffer;
if (l_ctx.mcp_dead)
goto out;
/* Copy message to MCP buffer */
memcpy(msg, cmd, sizeof(*msg));
/* Send MCP notification, with cmd_id as payload for debug purpose */
nq_session_notify(&l_ctx.mcp_session.nq_session, l_ctx.mcp_session.sid,
cmd_id);
/* Update MCP log */
mutex_lock(&l_ctx.last_cmds_mutex);
cmd_info->state = SENT;
mutex_unlock(&l_ctx.last_cmds_mutex);
ret = wait_mcp_notification();
if (ret)
goto out;
/* Check response ID */
if (msg->rsp_header.rsp_id != (cmd_id | FLAG_RESPONSE)) {
ret = -EBADE;
mc_dev_err(ret, "MCP command got invalid response (0x%X)",
msg->rsp_header.rsp_id);
goto out;
}
/* Convert result */
switch (msg->rsp_header.result) {
case MC_MCP_RET_OK:
err = 0;
break;
case MC_MCP_RET_ERR_CLOSE_TASK_FAILED:
err = -EAGAIN;
break;
case MC_MCP_RET_ERR_NO_MORE_SESSIONS:
err = -EBUSY;
break;
case MC_MCP_RET_ERR_OUT_OF_RESOURCES:
err = -ENOSPC;
break;
case MC_MCP_RET_ERR_UNKNOWN_UUID:
err = -ENOENT;
break;
case MC_MCP_RET_ERR_WRONG_PUBLIC_KEY:
err = -EKEYREJECTED;
break;
case MC_MCP_RET_ERR_SERVICE_BLOCKED:
err = -ECONNREFUSED;
break;
case MC_MCP_RET_ERR_SERVICE_LOCKED:
err = -ECONNABORTED;
break;
case MC_MCP_RET_ERR_SYSTEM_NOT_READY:
err = -EAGAIN;
break;
case MC_MCP_RET_ERR_DOWNGRADE_NOT_AUTHORIZED:
err = -EPERM;
break;
case MC_MCP_RET_ERR_SECURITY:
err = -EACCES;
break;
default:
err = -EPERM;
}
/* Copy response back to caller struct */
memcpy(cmd, msg, sizeof(*cmd));
out:
/* Update MCP log */
mutex_lock(&l_ctx.last_cmds_mutex);
if (ret) {
cmd_info->state = FAILED;
cmd_info->errno = -ret;
} else {
cmd_info->state = COMPLETE;
cmd_info->errno = -err;
cmd_info->result = msg->rsp_header.result;
/* For open session: get SID */
if (!err && out_session_id)
cmd_info->session_id = *out_session_id;
}
mutex_unlock(&l_ctx.last_cmds_mutex);
mutex_unlock(&l_ctx.buffer_mutex);
if (ret) {
mc_dev_err(ret, "%s: sending failed", cmd_to_string(cmd_id));
return ret;
}
if (err) {
if (cmd_id == MC_MCP_CMD_CLOSE_SESSION && err == -EAGAIN)
mc_dev_devel("%s: try again",
cmd_to_string(cmd_id));
else
mc_dev_err(err, "%s: res %d", cmd_to_string(cmd_id),
msg->rsp_header.result);
return err;
}
return 0;
}
static inline int __mcp_get_version(struct mc_version_info *version_info)
{
union mcp_message cmd;
u32 version;
int ret;
if (protocol_is_fe())
return protocol_mc_get_version(version_info);
version = MC_VERSION(MCDRVMODULEAPI_VERSION_MAJOR,
MCDRVMODULEAPI_VERSION_MINOR);
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_GET_MOBICORE_VERSION;
ret = mcp_cmd(&cmd, 0, NULL, NULL);
if (ret)
return ret;
memcpy(version_info, &cmd.rsp_get_version.version_info,
sizeof(*version_info));
/*
* The CMP version is meaningless in this case, and is replaced
* by the driver's own version.
*/
version_info->version_nwd = version;
return 0;
}
int mcp_get_version(struct mc_version_info *version_info)
{
static struct mc_version_info static_version_info;
/* If cache empty, get version from the SWd and cache it */
if (!static_version_info.version_nwd) {
int ret = __mcp_get_version(&static_version_info);
if (ret)
return ret;
}
/* Copy cached version */
memcpy(version_info, &static_version_info, sizeof(*version_info));
nq_set_version_ptr(static_version_info.product_id);
return 0;
}
int mcp_load_token(const struct mcp_buffer_map *map)
{
union mcp_message cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_LOAD_TOKEN;
cmd.cmd_load_token.wsm_data_type = map->type;
cmd.cmd_load_token.adr_load_data = map->addr;
cmd.cmd_load_token.ofs_load_data = map->offset;
cmd.cmd_load_token.len_load_data = map->length;
return mcp_cmd(&cmd, 0, NULL, NULL);
}
int mcp_open_session(struct mcp_session *session, struct mcp_open_info *info,
bool tci_in_use)
{
static DEFINE_MUTEX(local_mutex);
struct mcp_buffer_map map;
union mcp_message cmd;
int ret;
if (protocol_is_fe()) {
ret = protocol_mc_open_session(session, info);
if (ret)
return ret;
/* Add to list of sessions */
mutex_lock(&l_ctx.sessions_lock);
list_add_tail(&session->list, &l_ctx.sessions);
mutex_unlock(&l_ctx.sessions_lock);
return 0;
}
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_OPEN_SESSION;
/* Data */
cmd.cmd_open.uuid = *info->uuid;
tee_mmu_buffer(info->ta_mmu, &map);
cmd.cmd_open.wsm_data_type = map.type;
cmd.cmd_open.adr_load_data = map.addr;
cmd.cmd_open.ofs_load_data = map.offset;
cmd.cmd_open.len_load_data = map.length;
/* Buffer */
if (tci_in_use) {
tee_mmu_buffer(info->tci_mmu, &map);
cmd.cmd_open.wsmtype_tci = map.type;
cmd.cmd_open.adr_tci_buffer = map.addr;
cmd.cmd_open.ofs_tci_buffer = map.offset;
cmd.cmd_open.len_tci_buffer = map.length;
} else {
cmd.cmd_open.wsmtype_tci = WSM_INVALID;
}
/* Reset unexpected notification */
mutex_lock(&local_mutex);
l_ctx.unexp_notif.session_id = SID_MCP; /* Cannot be */
cmd.cmd_open.cmd_open_data.mclf_magic = MC_GP_CLIENT_AUTH_MAGIC;
/* Send MCP open command */
ret = mcp_cmd(&cmd, 0, &cmd.rsp_open.session_id, &cmd.cmd_open.uuid);
/* Make sure we have a valid session ID */
if (!ret && !cmd.rsp_open.session_id)
ret = -EBADE;
if (!ret) {
session->sid = cmd.rsp_open.session_id;
/* Add to list of sessions */
mutex_lock(&l_ctx.sessions_lock);
list_add_tail(&session->list, &l_ctx.sessions);
mutex_unlock(&l_ctx.sessions_lock);
/* Check for spurious notification */
mutex_lock(&l_ctx.unexp_notif_mutex);
if (l_ctx.unexp_notif.session_id == session->sid) {
mutex_lock(&session->exit_code_lock);
session->exit_code = l_ctx.unexp_notif.payload;
mutex_unlock(&session->exit_code_lock);
nq_session_state_update(&session->nq_session,
NQ_NOTIF_RECEIVED);
complete(&session->completion);
}
mutex_unlock(&l_ctx.unexp_notif_mutex);
}
mutex_unlock(&local_mutex);
return ret;
}
/*
* Legacy and GP TAs close differently:
* - GP TAs always send a notification with payload, whether on close or crash
* - Legacy TAs only send a notification with payload on crash
* - GP TAs may take time to close, and we get -EAGAIN back from mcp_cmd
* - Legacy TAs always close when asked, unless they are driver in which case
* they just don't close at all
*/
int mcp_close_session(struct mcp_session *session)
{
union mcp_message cmd;
/* ret's value is always set, but some compilers complain */
int ret = -ENXIO;
if (protocol_is_fe()) {
ret = protocol_mc_close_session(session);
} else {
/* Signal a potential waiter that SWd session is going away */
complete(&session->completion);
/* Send MCP command */
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_CLOSE_SESSION;
cmd.cmd_close.session_id = session->sid;
ret = mcp_cmd(&cmd, cmd.cmd_close.session_id, NULL, NULL);
}
mutex_lock(&l_ctx.sessions_lock);
if (!ret) {
session->state = MCP_SESSION_CLOSED;
list_del(&session->list);
nq_session_exit(&session->nq_session);
} else {
/* Something is not right, assume session is still running */
session->state = MCP_SESSION_CLOSE_FAILED;
}
mutex_unlock(&l_ctx.sessions_lock);
mc_dev_devel("close session %x ret %d state %s",
session->sid, ret, state_to_string(session->state));
return ret;
}
/*
* Session is to be removed from NWd records as SWd has been wiped clean
*/
void mcp_cleanup_session(struct mcp_session *session)
{
mutex_lock(&l_ctx.sessions_lock);
session->state = MCP_SESSION_CLOSED;
list_del(&session->list);
nq_session_exit(&session->nq_session);
mutex_unlock(&l_ctx.sessions_lock);
}
int mcp_map(u32 session_id, struct tee_mmu *mmu, u32 *sva)
{
struct mcp_buffer_map map;
union mcp_message cmd;
int ret;
if (protocol_is_fe())
return protocol_mc_map(session_id, mmu, sva);
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_MAP;
cmd.cmd_map.session_id = session_id;
tee_mmu_buffer(mmu, &map);
cmd.cmd_map.wsm_type = map.type;
cmd.cmd_map.adr_buffer = map.addr;
cmd.cmd_map.ofs_buffer = map.offset;
cmd.cmd_map.len_buffer = map.length;
cmd.cmd_map.flags = map.flags;
ret = mcp_cmd(&cmd, session_id, NULL, NULL);
if (!ret) {
*sva = cmd.rsp_map.secure_va;
atomic_inc(&g_ctx.c_maps);
}
return ret;
}
int mcp_unmap(u32 session_id, const struct mcp_buffer_map *map)
{
union mcp_message cmd;
int ret;
if (protocol_is_fe())
return protocol_mc_unmap(session_id, map);
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_UNMAP;
cmd.cmd_unmap.session_id = session_id;
cmd.cmd_unmap.wsm_type = map->type;
cmd.cmd_unmap.virtual_buffer_len = map->length;
cmd.cmd_unmap.secure_va = map->secure_va;
ret = mcp_cmd(&cmd, session_id, NULL, NULL);
if (!ret)
atomic_dec(&g_ctx.c_maps);
return ret;
}
static int mcp_close(void)
{
union mcp_message cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_header.cmd_id = MC_MCP_CMD_CLOSE_MCP;
return mcp_cmd(&cmd, 0, NULL, NULL);
}
int mcp_notify(struct mcp_session *session)
{
if (l_ctx.mcp_dead)
return -EHOSTUNREACH;
if (session->sid == SID_MCP)
mc_dev_devel("notify MCP");
else
mc_dev_devel("notify session %x", session->sid);
if (protocol_is_fe())
return protocol_mc_notify(session);
/* Put notif_count as payload for debug purpose */
return nq_session_notify(&session->nq_session, session->sid,
++session->notif_count);
}
static inline void session_notif_handler(struct mcp_session *session, u32 id,
u32 payload)
{
mutex_lock(&l_ctx.sessions_lock);
mc_dev_devel("MCP notif from session %x exit code %d state %d",
id, payload, session ? session->state : -1);
if (session) {
/* TA has terminated */
if (payload) {
/* Update exit code, or not */
mutex_lock(&session->exit_code_lock);
session->exit_code = payload;
mutex_unlock(&session->exit_code_lock);
}
nq_session_state_update(&session->nq_session,
NQ_NOTIF_RECEIVED);
/* Unblock waiter */
complete(&session->completion);
}
mutex_unlock(&l_ctx.sessions_lock);
/* Unknown session, probably being started */
if (!session) {
mutex_lock(&l_ctx.unexp_notif_mutex);
l_ctx.unexp_notif.session_id = id;
l_ctx.unexp_notif.payload = payload;
mutex_unlock(&l_ctx.unexp_notif_mutex);
}
}
static void mcp_notif_handler(u32 id, u32 payload)
{
if (id == SID_MCP) {
/* MCP notification */
mc_dev_devel("notification from MCP");
complete(&l_ctx.mcp_session.completion);
} else {
/* Session notification */
struct mcp_session *session = NULL, *candidate;
mutex_lock(&l_ctx.sessions_lock);
list_for_each_entry(candidate, &l_ctx.sessions, list) {
if (candidate->sid == id) {
session = candidate;
break;
}
}
mutex_unlock(&l_ctx.sessions_lock);
/* session is NULL if id not found */
session_notif_handler(session, id, payload);
}
}
static int debug_sessions(struct kasnprintf_buf *buf)
{
struct mcp_session *session;
int ret;
/* Header */
ret = kasnprintf(buf, "%20s %4s %-15s %-11s %4s\n",
"CPU clock", "ID", "state", "notif state", "ec");
if (ret < 0)
return ret;
mutex_lock(&l_ctx.sessions_lock);
list_for_each_entry(session, &l_ctx.sessions, list) {
const char *state_str;
u64 cpu_clk;
s32 err = 0;
state_str = nq_session_state(&session->nq_session, &cpu_clk);
mcp_get_err(session, &err);
ret = kasnprintf(buf, "%20llu %4x %-15s %-11s %4d\n", cpu_clk,
session->sid, state_to_string(session->state),
state_str, err);
if (ret < 0)
break;
}
mutex_unlock(&l_ctx.sessions_lock);
return ret;
}
static ssize_t debug_sessions_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
return debug_generic_read(file, user_buf, count, ppos,
debug_sessions);
}
static const struct file_operations debug_sessions_ops = {
.read = debug_sessions_read,
.llseek = default_llseek,
.open = debug_generic_open,
.release = debug_generic_release,
};
static inline int show_log_entry(struct kasnprintf_buf *buf,
struct command_info *cmd_info)
{
const char *state_str = "unknown";
switch (cmd_info->state) {
case UNUSED:
state_str = "unused";
break;
case PENDING:
state_str = "pending";
break;
case SENT:
state_str = "sent";
break;
case COMPLETE:
state_str = "complete";
break;
case FAILED:
state_str = "failed";
break;
}
return kasnprintf(buf, "%20llu %5d %-16s %5x %-8s %5d %6d%s\n",
cmd_info->cpu_clk, cmd_info->pid,
cmd_to_string(cmd_info->id), cmd_info->session_id,
state_str, cmd_info->errno, cmd_info->result,
cmd_info->uuid_str);
}
static int debug_last_cmds(struct kasnprintf_buf *buf)
{
struct command_info *cmd_info;
int i, ret = 0;
/* Initialize MCP log */
mutex_lock(&l_ctx.last_cmds_mutex);
ret = kasnprintf(buf, "%20s %5s %-16s %5s %-8s %5s %6s %s\n",
"CPU clock", "PID", "command", "S-ID",
"state", "errno", "result", "UUID");
if (ret < 0)
goto out;
cmd_info = &l_ctx.last_cmds[l_ctx.last_cmds_index];
if (cmd_info->state != UNUSED)
/* Buffer has wrapped around, dump end (oldest records) */
for (i = l_ctx.last_cmds_index; i < LAST_CMDS_SIZE; i++) {
ret = show_log_entry(buf, cmd_info++);
if (ret < 0)
goto out;
}
/* Dump first records */
cmd_info = &l_ctx.last_cmds[0];
for (i = 0; i < l_ctx.last_cmds_index; i++) {
ret = show_log_entry(buf, cmd_info++);
if (ret < 0)
goto out;
}
out:
mutex_unlock(&l_ctx.last_cmds_mutex);
return ret;
}
static ssize_t debug_last_cmds_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
return debug_generic_read(file, user_buf, count, ppos, debug_last_cmds);
}
static const struct file_operations debug_last_cmds_ops = {
.read = debug_last_cmds_read,
.llseek = default_llseek,
.open = debug_generic_open,
.release = debug_generic_release,
};
int mcp_init(void)
{
l_ctx.buffer = nq_get_mcp_buffer();
mutex_init(&l_ctx.buffer_mutex);
init_completion(&l_ctx.mcp_session.completion);
/* Setup notification queue mutex */
mcp_session_init(&l_ctx.mcp_session);
l_ctx.mcp_session.sid = SID_MCP;
mutex_init(&l_ctx.unexp_notif_mutex);
INIT_LIST_HEAD(&l_ctx.sessions);
mutex_init(&l_ctx.sessions_lock);
mutex_init(&l_ctx.last_cmds_mutex);
l_ctx.timeout_period = MCP_TIMEOUT;
nq_register_notif_handler(mcp_notif_handler, false);
l_ctx.tee_stop_notifier.notifier_call = tee_stop_notifier_fn;
nq_register_tee_stop_notifier(&l_ctx.tee_stop_notifier);
return 0;
}
void mcp_exit(void)
{
mark_mcp_dead();
nq_unregister_tee_stop_notifier(&l_ctx.tee_stop_notifier);
}
int mcp_start(void)
{
/* Create debugfs sessions and last commands entries */
debugfs_create_file("mcp_sessions", 0400, g_ctx.debug_dir, NULL,
&debug_sessions_ops);
debugfs_create_file("last_mcp_commands", 0400, g_ctx.debug_dir, NULL,
&debug_last_cmds_ops);
debugfs_create_u32("mcp_timeout", 0600, g_ctx.debug_dir,
&l_ctx.timeout_period);
return 0;
}
void mcp_stop(void)
{
mcp_close();
}