kernel-brax3-ubuntu-touch/drivers/tee/gud/510/MobiCoreDriver/fastcall.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

465 lines
9.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2013-2019 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/device.h>
#include <linux/debugfs.h>
#include <linux/sched.h> /* local_clock */
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/clock.h> /* local_clock */
#endif
#include "mci/mcifc.h"
#include "platform.h" /* MC_SMC_FASTCALL */
#include "main.h"
#include "fastcall.h"
#include "nq.h"
/* Unknown SMC Function Identifier (SMC Calling Convention) */
#define UNKNOWN_SMC -1
/* Use the arch_extension sec pseudo op before switching to secure world */
#if defined(__GNUC__) && \
defined(__GNUC_MINOR__) && \
defined(__GNUC_PATCHLEVEL__) && \
((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)) \
>= 40502
#ifndef CONFIG_ARM64
#define MC_ARCH_EXTENSION_SEC
#endif
#endif
/* Base for all fastcalls, do not use outside of other structs */
union fc_common {
struct {
u32 cmd;
u32 param[3];
} in;
struct {
u32 resp;
u32 ret;
u32 param[2];
} out;
};
union fc_init {
union fc_common common;
struct {
u32 cmd;
u32 base;
u32 nq_info;
u32 mcp_info;
} in;
struct {
u32 resp;
u32 ret;
u32 flags;
u32 rfu;
} out;
};
union fc_info {
union fc_common common;
struct {
u32 cmd;
u32 ext_info_id;
} in;
struct {
u32 resp;
u32 ret;
u32 state;
u32 ext_info;
} out;
};
union fc_trace {
union fc_common common;
struct {
u32 cmd;
u32 buffer_low;
u32 buffer_high;
u32 size;
} in;
struct {
u32 resp;
u32 ret;
} out;
};
union fc_nsiq {
union fc_common common;
struct {
u32 cmd;
u32 debug_ret;
u32 debug_session_id;
u32 debug_payload;
} in;
struct {
u32 resp;
u32 ret;
u32 code;
} out;
};
union fc_yield {
union fc_common common;
struct {
u32 cmd;
u32 debug_ret;
u32 debug_session_id;
u32 debug_payload;
} in;
struct {
u32 resp;
u32 ret;
u32 code;
} out;
};
#ifdef MC_TEE_HOTPLUG
union fc_cpu_off {
union fc_common common;
struct {
u32 cmd;
} in;
struct {
u32 resp;
u32 ret;
} out;
};
#endif
/* Structure to log SMC calls */
struct smc_log_entry {
u64 cpu_clk;
int cpu_id;
union fc_common fc;
};
#define SMC_LOG_SIZE 1024
static struct smc_log_entry smc_log[SMC_LOG_SIZE];
static int smc_log_index;
/*
* convert fast call return code to linux driver module error code
*/
static int convert_fc_ret(u32 ret)
{
switch (ret) {
case MC_FC_RET_OK:
return 0;
case MC_FC_RET_ERR_INVALID:
return -EINVAL;
case MC_FC_RET_ERR_ALREADY_INITIALIZED:
return -EBUSY;
default:
return -EFAULT;
}
}
/*
* __smc() - fast call to MobiCore
*
* @data: pointer to fast call data
*/
static inline int __smc(union fc_common *fc, const char *func)
{
int ret = 0;
/* Log SMC call */
smc_log[smc_log_index].cpu_clk = local_clock();
smc_log[smc_log_index].cpu_id = raw_smp_processor_id();
smc_log[smc_log_index].fc = *fc;
if (++smc_log_index >= SMC_LOG_SIZE)
smc_log_index = 0;
#ifdef MC_SMC_FASTCALL
ret = smc_fastcall(fc, sizeof(*fc));
#else /* MC_SMC_FASTCALL */
{
#ifdef CONFIG_ARM64
/* SMC expect values in x0-x3 */
register u64 reg0 __asm__("x0") = fc->in.cmd;
register u64 reg1 __asm__("x1") = fc->in.param[0];
register u64 reg2 __asm__("x2") = fc->in.param[1];
register u64 reg3 __asm__("x3") = fc->in.param[2];
/*
* According to AARCH64 SMC Calling Convention (ARM DEN 0028A),
* section 3.1: registers x4-x17 are unpredictable/scratch
* registers. So we have to make sure that the compiler does
* not allocate any of those registers by letting him know that
* the asm code might clobber them.
*/
__asm__ volatile (
"smc #0\n"
: "+r"(reg0), "+r"(reg1), "+r"(reg2), "+r"(reg3)
:
: "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11",
"x12", "x13", "x14", "x15", "x16", "x17"
);
#else /* CONFIG_ARM64 */
/* SMC expect values in r0-r3 */
register u32 reg0 __asm__("r0") = fc->in.cmd;
register u32 reg1 __asm__("r1") = fc->in.param[0];
register u32 reg2 __asm__("r2") = fc->in.param[1];
register u32 reg3 __asm__("r3") = fc->in.param[2];
__asm__ volatile (
#ifdef MC_ARCH_EXTENSION_SEC
/*
* This pseudo op is supported and required from
* binutils 2.21 on
*/
".arch_extension sec\n"
#endif /* MC_ARCH_EXTENSION_SEC */
"smc #0\n"
: "+r"(reg0), "+r"(reg1), "+r"(reg2), "+r"(reg3)
);
#endif /* !CONFIG_ARM64 */
/* set response */
fc->out.resp = reg0;
fc->out.ret = reg1;
fc->out.param[0] = reg2;
fc->out.param[1] = reg3;
#ifdef CONFIG_ARM64
/* The TEE has not been loaded */
if (reg0 == UNKNOWN_SMC)
ret = -EIO;
#endif /* CONFIG_ARM64 */
}
#endif /* !MC_SMC_FASTCALL */
if (ret) {
mc_dev_err(ret, "failed for %s", func);
} else {
ret = convert_fc_ret(fc->out.ret);
if (ret)
mc_dev_err(ret, "%s failed (%x)", func, fc->out.ret);
}
return ret;
}
#define smc(__fc__) __smc(__fc__.common, __func__)
int fc_init(uintptr_t addr, ptrdiff_t off, size_t q_len, size_t buf_len)
{
union fc_init fc;
#ifdef CONFIG_ARM64
u32 addr_high = (u32)(addr >> 32);
#else
u32 addr_high = 0;
#endif
/* Call the INIT fastcall to setup MobiCore initialization */
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_FC_INIT;
/* base address of mci buffer PAGE_SIZE (default is 4KB) aligned */
fc.in.base = (u32)addr;
/* notification buffer start/length [16:16] [start, length] */
fc.in.nq_info = (u32)(((addr_high & 0xFFFF) << 16) | (q_len & 0xFFFF));
/* mcp buffer start/length [16:16] [start, length] */
fc.in.mcp_info = (u32)((off << 16) | (buf_len & 0xFFFF));
mc_dev_devel("cmd=0x%08x, base=0x%08x, nq_info=0x%08x, mcp_info=0x%08x",
fc.in.cmd, fc.in.base, fc.in.nq_info,
fc.in.mcp_info);
return smc(&fc);
}
int fc_info(u32 ext_info_id, u32 *state, u32 *ext_info)
{
union fc_info fc;
int ret = 0;
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_FC_INFO;
fc.in.ext_info_id = ext_info_id;
ret = smc(&fc);
if (ret) {
if (state)
*state = MC_STATUS_NOT_INITIALIZED;
if (ext_info)
*ext_info = 0;
mc_dev_err(ret, "failed for index %d", ext_info_id);
} else {
if (state)
*state = fc.out.state;
if (ext_info)
*ext_info = fc.out.ext_info;
}
return ret;
}
int fc_trace_init(phys_addr_t buffer, u32 size)
{
union fc_trace fc;
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_FC_MEM_TRACE;
fc.in.buffer_low = (u32)buffer;
#ifdef CONFIG_ARM64
fc.in.buffer_high = (u32)(buffer >> 32);
#endif
fc.in.size = size;
return smc(&fc);
}
int fc_trace_set_level(u32 level)
{
union fc_trace fc;
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_FC_MEM_TRACE;
fc.in.buffer_low = level;
return smc(&fc);
}
int fc_trace_deinit(void)
{
return fc_trace_init(0, 0);
}
/* sid, payload only used for debug purpose */
int fc_nsiq(u32 session_id, u32 payload)
{
int ret;
union fc_nsiq fc;
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_SMC_N_SIQ;
fc.in.debug_session_id = session_id;
fc.in.debug_payload = payload;
/* Notice smc macro always returns zero if !MC_SMC_FASTCALL */
ret = smc(&fc);
if (ret)
return ret;
/* SWd return status must always be zero */
if (fc.out.ret)
return -EIO;
return 0;
}
/* sid, payload only used for debug purpose */
int fc_yield(u32 session_id, u32 payload, struct fc_s_yield *resp)
{
int ret;
union fc_yield fc;
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_SMC_N_YIELD;
fc.in.debug_session_id = session_id;
fc.in.debug_payload = payload;
/* Notice smc macro always returns zero if !MC_SMC_FASTCALL */
ret = smc(&fc);
if (ret)
return ret;
/* SWd return status must always be zero */
if (fc.out.ret)
return -EIO;
if (resp) {
resp->resp = fc.out.resp;
resp->ret = fc.out.ret;
resp->code = fc.out.code;
}
return 0;
}
#ifdef MC_TEE_HOTPLUG
int fc_cpu_off(void)
{
int ret;
union fc_cpu_off fc;
memset(&fc, 0, sizeof(fc));
fc.in.cmd = MC_FC_CPU_OFF;
/* Notice smc macro always returns zero if !MC_SMC_FASTCALL */
ret = smc(&fc);
if (ret)
return ret;
/* SWd return status must always be zero */
if (fc.out.ret)
return -EIO;
return 0;
}
#endif
static int show_smc_log_entry(struct kasnprintf_buf *buf,
struct smc_log_entry *entry)
{
return kasnprintf(buf, "%10d %20llu 0x%08x 0x%08x 0x%08x 0x%08x\n",
entry->cpu_id, entry->cpu_clk, entry->fc.in.cmd,
entry->fc.in.param[0], entry->fc.in.param[1],
entry->fc.in.param[2]);
}
/*
* Dump SMC log circular buffer, starting from oldest command. It is assumed
* nothing goes in any more at this point.
*/
int mc_fastcall_debug_smclog(struct kasnprintf_buf *buf)
{
int i, ret = 0;
ret = kasnprintf(buf, "%10s %20s %10s %-10s %-10s %-10s\n", "CPU id",
"CPU clock", "command", "param1", "param2", "param3");
if (ret < 0)
return ret;
if (smc_log[smc_log_index].cpu_clk)
/* Buffer has wrapped around, dump end (oldest records) */
for (i = smc_log_index; i < SMC_LOG_SIZE; i++) {
ret = show_smc_log_entry(buf, &smc_log[i]);
if (ret < 0)
return ret;
}
/* Dump first records */
for (i = 0; i < smc_log_index; i++) {
ret = show_smc_log_entry(buf, &smc_log[i]);
if (ret < 0)
return ret;
}
return ret;
}