// 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 #include #include /* local_clock */ #include #if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE #include /* 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; }